Introduction
Skip straight to the solution if you would rather avoid the rambling.
I had always used Google Analytics to check my blog traffic and analyze things like referrers. Static blog frameworks such as Hexo and Hugo make integration easy: just add some JavaScript to the head. I recently moved the blog to Astro. The old approach still works—execute JavaScript in the head to report events—but it comes at a performance cost. As everyone knows, Astro aims for the best possible frontend performance and as close to zero JavaScript execution as possible. Reporting events with JavaScript takes a bite out of that.

After some searching, most tutorials recommended partytown to run scripts off the main thread, keeping loading unblocked and performance intact. They all included example code, so I adapted one for my blog. Here was the result:

Reported traffic dropped straight to zero.
I could barely believe it. A long debugging session followed, but I never found the cause. Every relevant example I could find online used the same approach as mine. Honestly, do these people ever test their tutorials? None of this worked.
I had to leave it alone for two months and temporarily switch to umami for analytics.
It came back to mind over the last couple of days, and I could not let it go. Another long search finally turned up a solution in an obscure corner of GitHub.
Solution
Install @astrojs/partytown with your package manager.
Add the following code to <head>.
<script is:inline src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXX" type="text/partytown"></script>
<script is:inline type="text/partytown">
window.dataLayer = window.dataLayer || [];
window.gtag = function () {
dataLayer.push(arguments);
};
window.gtag('js', new Date());
window.gtag('config', 'G-XXXXXXXXX');
</script>
A few things to keep in mind:
is:inlineindicates that the script runs on the client.type="text/partytown"tells partytown to execute the script off the main thread.gtagmust be assigned as a function variable on the window object, rather than defined with a function declaration. Strange, I know.
Add the following to your Astro configuration file, usually astro.config.ts or astro.config.mjs.
import partytown from '@astrojs/partytown'
export default defineConfig({
// ...
integrations: [partytown({ config: { forward: ['dataLayer.push', 'gtag'] } })],
});
Most tutorials leave out the fact that gtag also needs to go in the forward array.
Done! Once committed and deployed, Google Analytics can report statistics normally again.
