Where should I put <script> tags in HTML markup?
When embedding JavaScript in an HTML document, where is the proper place to put the <script> tags and included JavaScript? I seem to recall that you are not supposed to place these in the <head> section, but placing at the beginning of the <body> section is bad, too, since the JavaScript will have to be parsed before the page is rendered completely. This seems to leave the end of the <body> section as a logical place for <script> tags.
So, where is the right place to put the <script> tags?
(Note: This question references a related discussion about moving JavaScript function calls from <a> tags to <script> tags. I’m specifically using jQuery, but more general answers are also appropriate.)
The best placement for <script> tags in HTML depends on several factors including performance, functionality needs, and modern web development practices. While traditionally placing scripts at the end of the <body> was recommended to prevent blocking page rendering, modern approaches offer more flexibility and performance optimization opportunities.
Contents
- Traditional Script Placement Approaches
- Modern Script Loading Strategies
- Performance Considerations
- jQuery-Specific Recommendations
- Best Practices by Use Case
- Advanced Optimization Techniques
Traditional Script Placement Approaches
End of the <body> Section
The traditional approach places <script> tags just before the closing </body> tag. This method prevents JavaScript from blocking the initial page rendering because the browser first renders all HTML content before executing the scripts.
<!DOCTYPE html>
<html>
<head>
<title>My Page</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h1>Welcome to my page</h1>
<p>This content loads quickly.</p>
<!-- Scripts at the end of body -->
<script src="jquery.js"></script>
<script src="app.js"></script>
</body>
</html>
Advantages:
- Fast content rendering
- No render-blocking behavior
- DOM elements are available when scripts execute
Beginning of the <body> Section
Placing scripts at the beginning of the <body> is generally discouraged because it blocks rendering. The browser must download and parse JavaScript before continuing to render the rest of the page content.
<body>
<!-- Bad practice - blocks rendering -->
<script src="jquery.js"></script>
<script src="app.js"></script>
<h1>Welcome to my page</h1>
<p>This content won't appear until scripts load.</p>
</body>
In the <head> Section
While placing scripts in the <head> section blocks rendering, there are cases where it’s necessary, especially for libraries that need to load before any DOM manipulation occurs.
<head>
<title>My Page</title>
<script src="jquery.js"></script> <!-- May be necessary here -->
</head>
Modern Script Loading Strategies
Async and Defer Attributes
Modern HTML provides the async and defer attributes to control script loading behavior without moving scripts to the end of the document.
Async Attribute:
<script src="analytics.js" async></script>
- Downloads without blocking parsing
- Executes as soon as download completes
- Not guaranteed to execute in order
- Best for independent scripts like analytics
Defer Attribute:
<script src="jquery.js" defer></script>
<script src="app.js" defer></script>
- Downloads without blocking parsing
- Executes after document parsing completes
- Maintains execution order
- Best for dependent scripts
Module Scripts
ES6 modules can use the type="module" attribute, which automatically applies deferred loading:
<script type="module" src="main.js"></script>
Performance Considerations
Render-Blocking Behavior
- Without async/defer: Scripts are render-blocking regardless of placement
- With async/defer: Scripts download asynchronously but may still block rendering under certain conditions
- Critical rendering path: Understanding the browser’s rendering process helps optimize script placement
Load Impact on Largest Contentful Paint (LCP)
- Scripts that block rendering can significantly impact LCP scores
- Non-critical scripts should be loaded after LCP elements
- Consider using
loading="lazy"for non-essential scripts
<script src="non-critical-feature.js" loading="lazy"></script>
jQuery-Specific Recommendations
Loading jQuery Before Dependent Scripts
When using jQuery, ensure it loads before any dependent scripts:
<!-- Option 1: With defer -->
<script src="jquery.js" defer></script>
<script src="my-jquery-code.js" defer></script>
<!-- Option 2: At end of body -->
<body>
<!-- Page content -->
<script src="jquery.js"></script>
<script src="my-jquery-code.js"></script>
</body>
jQuery CDN Placement
jQuery is often loaded from CDNs. Modern CDNs support features like preconnect and DNS-prefetch:
<head>
<link rel="preconnect" href="https://code.jquery.com">
<link rel="dns-prefetch" href="https://code.jquery.com">
<script src="https://code.jquery.com/jquery-3.7.1.min.js"
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
crossorigin="anonymous"></script>
</head>
Best Practices by Use Case
Critical Scripts
- Use inline in
<head>withasyncfor critical functionality - Consider placing critical JavaScript in the
<head>withdefer
<head>
<script>
// Critical inline script
function criticalFunction() {
// Essential functionality
}
</script>
<script src="critical-library.js" defer></script>
</head>
Third-Party Scripts
- Use
asyncfor independent third-party scripts (analytics, ads) - Use
deferfor scripts that depend on DOM or other scripts
Application Scripts
- Use
deferfor most application JavaScript - Place at end of
<body>if not using async/defer attributes
Advanced Optimization Techniques
Resource Hints
Implement resource hints to optimize script loading:
<head>
<!-- Preconnect to CDN -->
<link rel="preconnect" href="https://cdn.example.com">
<!-- DNS prefetch for domain -->
<link rel="dns-prefetch" href="https://cdn.example.com">
<!-- Preload critical scripts -->
<link rel="preload" href="critical.js" as="script">
</head>
Dynamic Script Loading
Load scripts dynamically when needed:
function loadScript(src, callback) {
const script = document.createElement('script');
script.src = src;
script.onload = callback;
document.body.appendChild(script);
}
// Load non-critical script after page interaction
document.addEventListener('click', () => {
loadScript('interactive-feature.js', () => {
// Initialize feature
});
});
Script Bundling and Code Splitting
- Bundle related scripts together
- Implement code splitting for large applications
- Use modern bundlers like Webpack, Rollup, or Vite
Performance Monitoring
Monitor script loading performance:
<script>
// Performance monitoring
window.addEventListener('load', () => {
const loadTime = performance.now();
console.log('Page loaded in:', loadTime, 'ms');
});
</script>
Conclusion
The optimal placement of <script> tags in HTML has evolved significantly with modern web development practices. While placing scripts at the end of the <body> remains a viable approach, the async and defer attributes provide more flexibility and better performance. For jQuery applications, ensure the library loads before dependent scripts, and consider using modern loading strategies. The best approach depends on your specific use case, performance requirements, and the criticality of your scripts. Always test and measure the impact of your script placement decisions on real-world performance metrics.