1513 words
8 min read

Cloudflare Web Analytics on Astro — Why Removing GA4 Unlocked Lighthouse 100

Cloudflare Web Analytics on Astro — Why Removing GA4 Unlocked Lighthouse 100

On a Saturday in April, this blog, built on Astro 6 and proxied through Cloudflare, was stuck at 88 mobile Lighthouse. The number had been moving between 83 and 88 across the previous month depending on image payload. Neither was passing a Lighthouse CI budget. The cover image took 4.5 seconds to render on throttled 4G. I looked at the stack. Cloudflare Web Analytics was already being injected at the proxy layer. Google Analytics 4 was loading roughly 70 KiB of gtag.js on top of it. Two analytics systems, one site, no reason for both.

PageSpeed Insights mobile report before Cloudflare Web Analytics migration showing 88 Performance, 100 Accessibility, 92 Best Practices, 100 SEO

I have watched this failure mode repeat across 20+ years of running observability in production: telecom portals, banking dashboards, enterprise SaaS marketing sites. A new measurement tool gets bolted on before anyone audits what the infrastructure layer already provides. Cloudflare Web Analytics has run server-side on this domain for over a year. GA4 on top was duplicate spend, paid in JavaScript execution time and GDPR exposure. Removing it closed the Lighthouse gap and eliminated third-party cookies in a single commit.

What the stacked analytics configuration looks like#

The problem rarely shows up in the source diff. It shows up in the Network tab.

A typical Astro layout adds GA4 the standard way, with a tag in <head>:

<!-- Astro layout with Google Analytics 4 (gtag.js) -->
<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>

Roughly 70 KiB gzipped for gtag.js with enhanced measurement enabled, an inline init block, and third-party requests to www.googletagmanager.com and www.google-analytics.com. Lighthouse flags it under “Reduce unused JavaScript” even for sites that never fire a custom event.

The replacement is nothing:

<!-- Cloudflare Web Analytics: no client script required -->
<!-- beacon.min.js is injected by Cloudflare proxy when -->
<!-- orange-cloud DNS is enabled. CSP must allow -->
<!-- static.cloudflareinsights.com in script-src. -->

The beacon is injected at the proxy layer. Aggregation happens at Cloudflare’s edge. The client pays for one small beacon instead of a 70 KiB analytics runtime, and no cookies are set.

Over twenty years of running observability in production, I have never had a performance budget where GA4’s gtag.js paid for itself on a content site. Every time the performance review came around, it was flagged and ignored.

Why this keeps happening#

Teams reach for Google Analytics as the default. Product managers know GA4 reports. Marketing knows GA4 dashboards. Bootcamp curricula teach GA4 snippets. So it gets installed even when the CDN layer already provides analytics, and nobody audits the stack for duplication.

Cloudflare Web Analytics runs at the proxy layer. When a domain is proxied through Cloudflare with orange-cloud DNS enabled, the static.cloudflareinsights.com/beacon.min.js script is injected server-side. The beacon is small, aggregation happens at Cloudflare’s edge, and no persistent identifier cookies are set by default.

Per Cloudflare’s public documentation, Web Analytics is available on all plans including Free. The service anonymizes IP at the edge and does not set tracking cookies. That puts it outside the default scope of most cookie consent requirements, though every team still needs its own legal review for its own jurisdiction.

Risk and blast radius#

Direct exposure breaks into three categories.

GDPR and ePrivacy is the first. GA4 uses cookies and processes personal data. It requires explicit consent in the EU and UK, and increasingly under US state privacy laws (CCPA, VCDPA, CPA). Cloudflare Web Analytics operates without tracking cookies. CNIL in France issued a ruling in February 2022 that Google Analytics deployments can fall outside GDPR compliance, specifically around data transfers to the United States.

Performance budget is the second. 70 KiB of JavaScript to parse and execute on every first page load. Main-thread work spikes on mid-tier Android.

Data sovereignty is the third. GA4 data resides in Google infrastructure, US-default.

Systemic exposure is larger. Every page across every visit carries the runtime cost. On a 1,000-page documentation site serving 100K monthly visitors, that is real energy, real latency, and a persistent third-party dependency in the critical render path. Cookie consent management platforms add 20-50 KiB more on top. Removing GA4 often removes the reason for the CMP.

Options compared#

ToolClient JS (gzipped)Tracking cookiesData residencyPriceFit
Google Analytics 4~70 KiBYesUS (Google)FreeFunnels, ad attribution, Google Ads integration
Cloudflare Web Analytics0 additional (edge beacon ~1 KiB)NoCloudflare edgeFree on all plansTraffic, Core Web Vitals, content sites
Plausible~1 KiBNoEU (Hetzner)$9/mo and upPrivacy-first, public dashboards
Fathom~1.6 KiBNoEU or Canada$15/mo and upPrivacy-first, simpler UX
PostHog (self-hosted)20-50 KiBConfigurableYour infrastructureInfra onlyProduct analytics, session replay, feature flags

The table should not be read as “Cloudflare wins.” It should be read as the question is what you measure. A content site that needs pageviews and Core Web Vitals has a different answer than an e-commerce funnel that needs attribution down to ad creative.

Framework: From GA4 to Cloudflare Web Analytics#

Layer 1 — Measure (week 1)#

  • Run Lighthouse on mobile and desktop, capture current scores
  • Run PageSpeed Insights and pull CrUX real-user data for LCP p75 if the domain meets the traffic threshold; otherwise use Cloudflare RUM or self-hosted web-vitals collection
  • Open DevTools Network tab and list every third-party origin loaded on the home page
  • Check response headers to identify your CDN: cf-ray for Cloudflare, x-served-by for Fastly, x-nf-request-id for Netlify, x-vercel-cache for Vercel
  • Audit the CDN dashboard for built-in analytics you may not have enabled: Cloudflare Web Analytics, Fastly Observability, Netlify Analytics, Vercel Web Analytics

Owner: platform engineer or senior frontend.

Layer 2 — Remove (week 2)#

Remove any analytics tool that duplicates what the CDN already provides.

Update CSP to allow the CDN beacon origin. For Cloudflare:

# CSP for Cloudflare Web Analytics on Astro + Cloudflare proxy
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' https://static.cloudflareinsights.com;
connect-src 'self' https://cloudflareinsights.com;
img-src 'self' data:;
style-src 'self' 'unsafe-inline';

Add fetchpriority="high" to the LCP image, meaning the hero image, first post card, or above-fold banner:

<!-- LCP candidate, Astro first-post card -->
<img
src="/images/posts/first-post.webp"
alt="First post cover"
fetchpriority="high"
loading="eager"
width="1200"
height="675"
/>

Audit every <link rel="preconnect"> and delete origins not used in the critical render path. Dead preconnects steal TCP slots from origins that actually matter.

Owner: frontend lead plus security for CSP review.

Layer 3 — Gate in CI (week 3 onward)#

Add Lighthouse CI to the build pipeline. Fail the build on regressions:

# .github/workflows/lighthouse.yml — Lighthouse CI gate
name: Lighthouse
on: [push, pull_request]
jobs:
lhci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci && npm run build
- uses: treosh/lighthouse-ci-action@v11
with:
urls: |
https://heyvaldemar.com/
https://heyvaldemar.com/latest-post/
budgetPath: ./budget.json
uploadArtifacts: true

Monitor Cloudflare RUM p75 LCP weekly. Synthetic Lighthouse is a benchmark, not the truth. Real users on newer phones over better networks are usually 30-50% faster than the Moto G Power / Slow 4G synthetic model.

Add a pre-push Git hook running astro check && astro build so a broken build never reaches the remote:

#!/usr/bin/env bash
# .git/hooks/pre-push — block push on failed Astro build
ZERO_SHA="0000000000000000000000000000000000000000"
while read -r local_ref local_sha remote_ref remote_sha; do
# Skip verification on branch delete
[ "$local_sha" = "$ZERO_SHA" ] && exit 0
done
npx astro check && npx astro build

Owner: platform engineer.

Tradeoffs#

Cloudflare Web Analytics is less featureful than GA4. No funnels. No deep custom event schemas. No native Google Ads integration. For content sites, documentation, and most engineering-led blogs, none of this matters. For conversion-heavy e-commerce with retargeting pipelines, GA4 may still earn its weight, though the stronger answer is usually a privacy-first product analytics tool (Plausible, Fathom, or self-hosted PostHog) rather than client-side GA4.

The other tradeoff is CDN alignment. If the site moves off Cloudflare, the beacon goes with it. Not a hard lock-in, since competing CDNs offer similar first-party analytics, but worth naming in any architecture review.

Cost of the alternative is concrete. 70 KiB of JavaScript on every pageview. A cookie consent banner. GDPR exposure. A persistent regression against any performance budget. On mid-tier Android over a mid-tier network, that 70 KiB is a noticeable LCP penalty. I have seen Lighthouse scores camp at 92-94 for months because a team was unwilling to question the GA4 install.

PageSpeed Insights mobile report after Cloudflare Web Analytics migration showing 94 Performance, 100 Accessibility, 100 Best Practices, 100 SEO

The closing argument#

The strongest case against GA4 on a content site in 2026 is not privacy and not performance. It is supply chain. Every third-party script in your critical render path is code you did not write, cannot audit, and inherit the blast radius of. Cloudflare Web Analytics is not better because it is faster. It is better because it is one fewer trust boundary.

Privacy alignment is a bonus. LCP improvement is a bonus. The architectural position is that observability tooling should come from the infrastructure layer you already trust, not bolted on top of it.

Sources#

Discussion#

If you have removed GA4 on a production site, or kept it and have the numbers to justify it, drop a comment below. Counterarguments welcome. For longer back-and-forth with senior practitioners, join the discussion on Discord.


Vladimir Mikhalev

Docker Captain  ·  IBM Champion  ·  AWS Community Builder

The Verdict — production-tested analysis on YouTube.

Cloudflare Web Analytics on Astro — Why Removing GA4 Unlocked Lighthouse 100
https://heyvaldemar.com/cloudflare-web-analytics-astro-lighthouse-100/
Author
Vladimir Mikhalev
Published
2026-04-18
License
CC BY-NC-SA 4.0

Discussion