Allowing Google Tag Manager through the Content-Security-Policy

2023-06-26

Predictable sequel to PR #3: added Google Tag Manager, opened the site, and the browser console promptly filled up with CSP errors. The Content-Security-Policy that ships with the Vercel blog template is tight by default — script-src 'self' plus a small allowlist — and googletagmanager.com wasn't on it.

The fix

next.config.js holds a ContentSecurityPolicy constant that gets baked into a security header. Two lines added:

  media-src 'none';
  connect-src *;
  font-src 'self';
+ script-src: 'unsafe-inline' https://www.googletagmanager.com;
+ img-src: www.googletagmanager.com;
  • script-src gets 'unsafe-inline' (because GTM injects inline scripts) and https://www.googletagmanager.com (for the GTM loader).
  • img-src gets www.googletagmanager.com because GTM's tracking pixels are images, and they'd get blocked just as hard as the scripts otherwise.

A small noscript fallback rearrangement

While I was in app/layout.tsx I also moved the <noscript> GTM iframe fallback to the top of <body>, which is where Google's docs recommend it. The original placement after the sidebar and main content technically worked but was out of spec.

About that 'unsafe-inline'

I'm aware that 'unsafe-inline' for script-src is one of those things a security review would correctly yell at me about. For a personal blog with no login, no form submission, and no user-generated content, the attack surface is near-zero. The alternative — nonce-based CSP with per-request nonces — is doable in Next.js but not free, and I'd rather keep the config simple until there's a real reason to tighten it.

The next PR is embarrassing

This fix has a bug. I wrote script-src: and img-src: (with colons). CSP directive syntax uses a space, not a colon. Shipping it anyway because I didn't catch it until I re-deployed and the CSP header was silently malformed. PR #5 is the one-character fix.


PR: https://github.com/StevieIsmagic/vercel_blog/pull/4