Google Analytics Is Slowing Down Your Website (HUGO + Netlify + GitHub Actions)
Google Analytics (GA4) is an essential tool for website owners. It’s how we keep track of who’s visiting, where they’re coming from, what pages they’re reading, and whether they’re taking the actions we want — from filling out a form to making a purchase.
All of that is great, but it does come with a cost: slower load times and unnecessary bloat — especially when loaded directly from Google’s servers. Over time, those extra milliseconds add up, impacting both user experience and your site’s performance scores.
Obviously, that won’t work if you’re serious about performance, privacy, and future-proofing your site.
The good news?
There’s a fix.
The Problem with the Default GA4 Snippet
Here’s what Google gives you by default:
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){ dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>
That works — but:
- It loads from Google’s domain, not yours
- It fires immediately, delaying your site’s render
- Adds unnecessary JavaScript bloat most sites don’t even use
How Do I Stop Google Analytics Slowing Down My Website?
The solution isn’t to give up analytics — it’s to load it smarter.
Here’s the approach I use on my own site:
- Proxy
gtag.js
through a Netlify Function, so it loads from your domain instead of Google’s - Delay loading with
requestIdleCallback()
(or asetTimeout()
fallback), so it never blocks your content - Keep the tracking ID secure using a Netlify environment variable
- Add cachebusting to make sure you always serve a fresh copy, even if Google updates the script
Step 1: The Environment Variable
Before we get started with our code, one small but important piece of this setup is the tracking ID.
Instead of hardcoding it in multiple places, we’ll keep things DRY by storing it in a single environment variable.
This way:
- You only update the ID in one place if it changes
- You avoid sprinkling the value across multiple templates
- Your Hugo templates and Netlify Functions both read from the same source
To add your GA tracking ID as an environment variable in Netlify, follow this guide.
For this tutorial, the variable should be named: GA_TRACKING_ID = G-XXXXXXXXXX
.
Once added, you can access it using:
// In your Netlify Function
const GA_ID = process.env.GA_TRACKING_ID;
<!-- In HUGO Templates -->
{{ getenv "GA_TRACKING_ID" }}
With the environment variable in place, we’re ready to build the Netlify Function that will proxy the gtag.js
script from Google and serve it from your own domain.
Step 2: The Netlify Function
Now that your environment variable is set, the next step is to proxy Google’s gtag.js script through a Netlify Function.
Why do this? Because by default, the script is served from Google’s domain (googletagmanager.com
). That creates a few problems:
- Extra DNS lookups: Every new third‑party domain adds a delay.
- Less control: You can’t set your own cache rules or headers on a third‑party script.
- Privacy concerns: Loading from a third‑party domain makes it easier for browsers to flag or block it.
By serving gtag.js
from your own domain, you still gain important advantages:
- Faster DNS resolution (no extra lookup)
- Full cache control (so you can decide how long browsers should store it)
- The script itself loads in a first‑party context, which plays nicer with evolving privacy rules
- A consistent way to inject updates without touching Google’s snippet directly
Now, let’s create the function that handles this proxying.
Create a file at netlify/functions/gtag.js:
// Netlify Function: Proxy Google Analytics gtag.js
// This fetches the GA script from Google's servers and serves it from your own domain,
// giving you cache control, a first-party context, and better performance.
export const handler = async () => {
// Get the GA Tracking ID from Netlify environment variables
const GA_ID = process.env.GA_TRACKING_ID;
// Fetch the latest gtag.js script from Google with your tracking ID
const res = await fetch(`https://www.googletagmanager.com/gtag/js?id=${GA_ID}`);
// If the request fails, return a 500 error with a simple comment
if (!res.ok) {
return {
statusCode: 500,
body: '// Failed to fetch gtag.js',
};
}
// Read the script contents as text
const js = await res.text();
// Return the script with proper headers
return {
statusCode: 200,
headers: {
// Ensure the browser treats this as JavaScript
'Content-Type': 'text/javascript; charset=UTF-8',
// Cache aggressively for a year; updates are forced by a cachebusting query string
'Cache-Control': 'public, max-age=31536000, immutable',
},
// Send the script content as the response body
body: js,
};
};
This fetches the latest Google tag script and serves it from your domain with a proper MIME type and long cache life.
Step 3: Hugo Template for Conditional GA Loading
The final piece is adding the script to your site — but only when it makes sense.
We don’t want Google Analytics loading in every environment.
For example:
- In development: It clutters your reports with test data.
- In staging: It may give a false picture of real users.
- In production: Yes, this is where we want it.
To handle this, Hugo allows us to check both the current environment and whether our GA tracking ID is set.
This ensures the script only loads when we’re in production and the environment variable exists.
In your <head>
element (wherever it may be located), add:
{{/* Pull the GA tracking ID from the Netlify environment variable */}}
{{ $gaID := getenv "GA_TRACKING_ID" }}
{{/* Only run the following block if:
1. The site is being built in the "production" environment
2. The GA tracking ID is actually set */}}
{{ if and (eq hugo.Environment "production") $gaID }}
<script>
// Define a function to load the GA script from our Netlify proxy
function loadGA() {
// Create a new <script> element
const script = document.createElement('script');
// Point it to our Netlify Function with a cachebusting query string
script.src = '/.netlify/functions/gtag?v={{ now.Unix }}';
script.async = true; // Load asynchronously so it doesn't block rendering
// Append the script to the <head>
document.head.appendChild(script);
// Initialize the GA data layer
window.dataLayer = window.dataLayer || [];
function gtag(){ dataLayer.push(arguments); }
gtag('js', new Date()); // Mark script start time
gtag('config', '{{ $gaID }}', {
cookie_domain: 'yourdomain.com',
cookie_flags: 'SameSite=None;Secure' // Future‑proof cookies
});
}
// Defer loading:
// - Prefer requestIdleCallback (if supported)
// - Otherwise, fall back to a 3‑second timeout
'requestIdleCallback' in window
? requestIdleCallback(loadGA, { timeout: 5000 })
: setTimeout(loadGA, 3000);
</script>
{{ end }}
Step 4: GitHub Action for Weekly Updates
The current setup will update your Google Analytics code on each deploy — which works fine as long as you’re actively updating your site.
The problem is, most of us don’t push changes every day (or even every week).
That means your gtag.js
could go stale if Google makes updates and you haven’t deployed recently.
To solve this, we schedule a GitHub Action that triggers a Netlify deploy once a week — even if nothing has changed in your repo. This guarantees your proxied GA script is always up to date.
I run mine every Tuesday at 8 AM EST (12 PM UTC). Why Tuesday?
- Early in the week, but not Monday (which often gets busy).
- Ensures the script is refreshed after the weekend, before the heaviest mid‑week traffic.
- Running it in the morning means the site is updated and cached for the rest of the day.
Here’s the workflow file:
# This is the name that will show up for the workflow in GitHub Actions
name: Update GA Script
on:
# Schedule this workflow to run automatically
schedule:
- cron: '0 2 * * *' # Every day at 2am UTC
# Allow manual runs from the GitHub Actions dashboard
workflow_dispatch:
jobs:
# Define the job that updates the GA script
update-ga:
# Use the latest Ubuntu runner for this job
runs-on: ubuntu-latest
steps:
# Step 1: Check out the repository so we can modify files
- uses: actions/checkout@v3
# Step 2: Download the latest gtag.js from Google
- name: Download gtag.js
run: curl -s https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX > static/js/gtag.js
# Step 3: Commit and push the updated gtag.js back to the repo
- name: Commit and push
run: |
# Configure git with a bot user identity
git config user.name "github-actions"
git config user.email "github-actions@github.com"
# Stage the downloaded gtag.js file
git add static/js/gtag.js
# Commit the change with a clear message
git commit -m "Update GA script"
# Push the commit to the repository, triggering a new Netlify deploy
git push
This ensures that:
- Your GA script is refreshed once a week, automatically
- You don’t have to remember to push a commit just to keep Analytics up to date
- Your site is always serving the latest Google Analytics code, even if your content hasn’t changed
Why Lighthouse Still Flags “Unused JavaScript”
You might see a message like this:
That’s because gtag.js
loads everything GA might ever need — not just the parts you use. Even though it’s deferred, Lighthouse still includes its full size in the audit.
- This isn’t hurting your user experience.
- You’re already deferring it.
- Real-world performance is fine.
Unless you’re building your own stripped-down GA proxy (or switching to server-side tracking), this is as optimized as it gets.
Bonus: Is Facebook Pixel Slowing Down Your Website?
Google Analytics isn’t the only script that can hurt your site’s performance. If you’re using the Facebook (now Meta) Pixel, you can face many of the same challenges.
Learn how to stop Facebook Pixel slowing down your website.
Final Thoughts
This approach isn’t just about speed. It’s about control.
By hosting GA yourself and delaying its execution, you gain:
- Better performance scores
- Less browser tracking noise
- And a smoother, more privacy-conscious foundation for the future web
If you’re running Hugo on Netlify (or any static framework), this takes just minutes to implement — and your Core Web Vitals will thank you.