StatelessID

Head or Body

Either works — the snippet is designed to fire on the page load event regardless of where it sits in the document. Head placement is slightly more reliable on pages with complex JavaScript that delays the body.

How the snippet fires

The tracking script listens for the browser's load event using window.onload = ... and also checks document.readyState immediately in case the page was already loaded when the script ran. This means it fires correctly whether it is in the <head>, at the end of <body>, or anywhere else in the DOM.

The beacon reports a load time based on performance.timing.loadEventEnd minus navigationStart. That measurement only makes sense after the load event fires, so the script waits for it regardless of its position in the HTML. There is no race condition caused by head versus body placement.

The practical advice: if your template has a natural footer partial or a "scripts" block at the bottom of the body, pasting there is fine and keeps your head clean. If you are on a platform that only lets you inject into the head, that works just as well.

Reasons to prefer head placement

If your page uses JavaScript frameworks that rewrite large portions of the DOM after the initial HTML renders — single-page apps, heavy React or Vue sites — placing the snippet in the head ensures the tracking script is parsed and ready before any framework code runs. Framework code that throws an unhandled error before the body closes could theoretically prevent body-placed scripts from executing in some older browsers.

Head placement also ensures the script is present during server-side rendering hydration. If the page is rendered by a server and then "hydrated" by client-side JavaScript, a head-placed script fires during the initial HTML rendering, not after the hydration pass.

For most plain HTML sites and CMS-driven sites, this distinction does not matter. Both head and body placement produce identical results.

Reasons to prefer body placement

Putting the snippet just before </body> follows a common convention for third-party scripts — it keeps the head lean and avoids any possibility of the script blocking HTML parsing before the browser gets to the visible content. The StatelessID script is tiny and does not block rendering regardless of placement, but body placement is a defensible habit.

WordPress child themes typically add scripts via wp_enqueue_script with a true argument for the in-footer parameter. That automatically places them before </body>. Using that convention means WordPress manages the placement and you do not need to touch footer.php directly.

Netlify snippet injection, GitHub Pages layout files, and most template-based platforms have a designated "scripts" section near the bottom of the body. Using that section is idiomatic for those platforms and makes your customization easier to find later.

Async, defer, and type attributes

The snippet as provided has no async or defer attribute. Because the script fires on the load event (not on DOMContentLoaded), adding defer does not change when the beacon fires. Adding async could theoretically cause the script to execute before the page load event if the download completes very early, but the internal onload handler guards against that.

Do not add type="module" to the tag. The script is written as a plain IIFE (immediately invoked function expression) and does not use module syntax. Adding type="module" changes script execution semantics in ways that are unnecessary and could cause issues in some environments.

Do not add a nonce or integrity hash to the snippet unless you generated those values from the actual asset.js content at a specific point in time. SRI hashes will break if the script file is ever updated on the CDN. If your CSP requires a nonce, apply it consistently to all inline scripts on the page through your server-side nonce generation, not by hardcoding a value in the snippet.

Troubleshooting

I moved the snippet from the body to the head and now I see fewer page views. This is almost certainly a coincidence in timing. The script fires on the load event in both locations. Check whether other changes were made to the template at the same time that might have removed the snippet from some page types.

My Content Security Policy blocks the script when it is in the head but allows it in the body. CSP applies to the whole page regardless of where scripts appear in the DOM. If CSP is blocking asset.js, add script-src https://statelessid.com and connect-src https://statelessid.com to your policy. The element position in the document has no effect on CSP.

I added defer to the snippet and load time data looks wrong. Remove defer. The load time calculation uses performance.timing which is only valid after the load event. The script already waits for that event internally — defer does not improve anything and in some edge cases can confuse the timing check.

Similar pages