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-srcgets'unsafe-inline'(because GTM injects inline scripts) andhttps://www.googletagmanager.com(for the GTM loader).img-srcgetswww.googletagmanager.combecause 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.