Content Security Policy in Next.js: Static Pages, Nonces, and Real‑World Trade‑Offs

Hero image for Content Security Policy in Next.js: Static Pages, Nonces, and Real‑World Trade‑Offs. Image by Bruce Barrow.
Hero image for 'Content Security Policy in Next.js: Static Pages, Nonces, and Real‑World Trade‑Offs.' Image by Bruce Barrow.

Content Security Policy is one of those security features that sounds simple until it meets a real web application.

The principle is good: tell the browser which sources are allowed for scripts, styles, images, frames, fonts, connections, and other resource types. Reduce the blast radius of crosssite scripting and injection attacks. Make dangerous surprises harder to execute.

Then the real site appears. Next.js injects framework scripts. Analytics needs to load. A consent tool wants inline configuration. A map embed uses an iframe. The CMS serves images from a CDN. Marketing wants a tag manager. Preview needs to work in another environment. Some pages are static. Some are dynamic. Suddenly the policy is either too weak to be meaningful or too strict to ship.

CSP is worth doing, but it is not a pastein header.


A Csp is a Browser Contract

A Content Security Policy is enforced by the browser. The server sends a header, and the browser uses it to decide which resources may load or execute.

That makes CSP different from many applicationlevel checks. It does not replace input validation, output encoding, authentication, or dependency hygiene. It adds a browserside boundary that can reduce damage if something slips through.

The Next.js documentation frames Content Security Policy as protection against threats such as crosssite scripting, clickjacking, and code injection. That is the right level of ambition. CSP is not a magic shield. It is one layer in a defensive system.

The practical question is how strict the policy can be without breaking the application or teaching everyone to ignore violations.


Static Pages Change the Nonce Conversation

Nonces are a common way to allow specific inline scripts or styles. The server generates a onetime value, includes it in the CSP header, and attaches the matching nonce to approved scripts.

That works naturally when a page is rendered per request. It becomes awkward for static pages because the page is generated at build time, before there is a requestspecific nonce. The Next.js CSP guide explains this directly: static pages are generated at build time, when no request or response headers exist, so nonce injection is not available in the same way.

That tradeoff matters for Next.js sites because static generation is often the right choice for performance, SEO, and reliability. Forcing a static content site into dynamic rendering only to support noncebased CSP may be a poor exchange.

There are three broad options:

  • keep static generation and use a CSP model that does not depend on request nonces
  • make specific routes dynamic where strict nonce behaviour is worth the cost
  • redesign inline script usage so the policy can stay simpler

For staticheavy routes, that may mean moving inline code into external files, using CSP hashes for genuinely static inline blocks, or waiting for framework support such as Subresource Integrity where it fits the rendering mode and bundler. MDN's `scriptsrc` reference is a useful grounding point here because it explains the difference between nonce sources, hash sources, strictdynamic, and broad allowances such as unsafeinline.

The wrong answer is pretending static and dynamic pages have the same CSP constraints.


Start by Inventorying What the Page Actually Loads

Before writing a policy, inspect the resources.

Which scripts load from the application itself? Which come from analytics, consent, tag managers, maps, video embeds, chat widgets, or monitoring tools? Which images come from the CMS or image CDN? Which API origins are used by browserside code? Which frames are embedded? Which styles are inline? Which fonts are served locally or remotely?

This is another place where CSP overlaps with performance work. A routelevel thirdparty script inventory helps security as well as Core Web Vitals. If nobody can list the origins, nobody can write a meaningful policy.

The first version of the policy should often run in reportonly mode. That lets the team see violations without breaking the site immediately. Reportonly is not the finish line, but it is a useful way to separate expected resource usage from accidental usage.


Third‑Party Scripts are the Hard Part

The easiest CSP is the one with no thirdparty scripts. Most production sites do not have that luxury.

Tag managers are especially awkward because they are designed to load and configure other scripts. A policy that allows the tag manager but not the scripts it injects will break tags. A policy that allows everything the tag manager might ever need becomes broad enough to lose much of its value.

That is not a reason to give up. It is a reason to govern the tag manager.

Ask whether every thirdparty origin is needed on every route. Ask whether inline configuration can move to a safer pattern. Ask whether a vendor supports nonce attributes, external configuration, serverside tagging, or more restricted domains. Ask whether the same script is being loaded through both application code and a tag manager.

Security policy should force these conversations. If it cannot, the policy will gradually become a list of exceptions nobody trusts.


Avoid Unsafe Directives Unless You Know the Cost

Many broken CSP implementations are fixed by adding broad allowances. unsafeinline makes inline scripts easier. unsafeeval helps libraries that compile or evaluate code at runtime. Wide wildcard sources stop things breaking when vendors change URLs.

Sometimes a temporary relaxation is necessary to keep a site working while the team migrates. It should still be treated as debt.

The problem is not that one directive is morally bad. The problem is that each broad allowance weakens the browser contract. If the goal is to reduce injection risk, the policy should not permanently allow the very behaviour it was meant to constrain.

Careful routelevel work helps. An admin route, a marketing page, an article page, and an embedded preview route may not need identical policy. The closer the policy matches the actual route, the less pressure there is to make it universally loose.


Next.js Scripts Need Deliberate Handling

Next.js gives teams a Script component for loading scripts with defined strategies. The scripts guide notes that additional attributes, including nonce, can be forwarded to the final script element. That is useful, but it does not remove the need to understand the rendering model.

For dynamically rendered pages, nonceaware scripts can fit neatly into a strict policy. For static pages, the same pattern may not apply. For thirdparty scripts loaded globally in a root layout or custom app, every route inherits the CSP and performance cost.

Script strategy, route ownership, and CSP should be designed together. If a script is not needed until after interaction, it should not force a weaker policy during initial render. If a script is only needed on one route, it should not expand the CSP for the whole site.


Search and Answer Engines Still Need the Page to Work

CSP mistakes can look like security work while quietly breaking discovery.

If a policy blocks image CDN assets, structured data injection, preview scripts, consent behaviour, analytics, fonts, or browserside data requests, the visible page may still seem close enough during a quick check. The rendered output can still be weaker than intended. Images disappear. Layout shifts. Product or article context fails to load. Internal widgets stop exposing links. Monitoring loses the signal that would have shown the problem.

Search engines and answer engines need stable rendered pages, not just strict headers. A CSP that improves security while damaging crawlable content, metadata, or structured data has not been designed carefully enough.

That does not mean weakening the policy whenever a vendor complains. It means checking important templates after enforcement: rendered text, canonical metadata, JSONLD parseability, images, forms, embeds, and routespecific scripts. Security controls should support the site's purpose, not accidentally obscure it.


Frames, Previews, and Admin Tools Need Special Care

Frame rules are often discovered late.

A strict frameancestors directive can prevent the site being embedded elsewhere, which is usually good. It can also break CMS live preview if the CMS needs to frame the page. A loose frame policy can make clickjacking easier. Preview domains, staging domains, CMS origins, and production domains need to be considered explicitly.

This is why preview should not be an afterthought. If Contentful preview, Draft Mode, or internal QA tooling needs embedded routes, the CSP has to allow those routes safely rather than accidentally.

The same applies to connectsrc. Browserside preview tools, monitoring, analytics, and APIs may all need outbound connections. List them intentionally.


Make Csp Deployment Incremental

Do not ship a strict CSP across a large existing site in one blind release.

A safer route is:

  1. Inventory resources on important templates.
  2. Add a reportonly policy.
  3. Review violations and remove dead resources.
  4. Tighten obvious sources.
  5. Move inline scripts or styles where practical.
  6. Decide which pages need dynamic nonce support and which should stay static.
  7. Enforce the policy on a limited route set.
  8. Expand once violations are understood.

That approach sounds slower. It is faster than breaking analytics, checkout, preview, or consent in production and then deleting the policy under pressure.


Csp Should Fail Loudly Enough to Maintain

A policy nobody monitors will rot.

When CSP blocks a script, the team needs a path for seeing and triaging the violation. Some violations are attacks or browser extensions. Some are legitimate regressions. Some are vendor changes. Some are old tools nobody knew still existed.

Without monitoring, the policy becomes either too permissive to matter or too brittle to keep.

Good CSP work leaves behind ownership:

  • who reviews new origins
  • who owns tag manager changes
  • how reportonly violations are triaged
  • how preview and staging differ from production
  • when a broad allowance must be revisited

Security controls need maintenance just like performance budgets and dependency updates.


Wrapping up

Content Security Policy in Next.js is not difficult because the header syntax is complicated. It is difficult because modern sites load a lot of code from a lot of places, and Next.js can render pages in several different ways.

Static pages, nonces, thirdparty scripts, tag managers, CMS preview, and routespecific behaviour all affect the shape of a workable policy.

The useful CSP is not the strictest header someone can write in isolation. It is the strongest policy the application can actually operate, monitor, and improve.

Key Takeaways

  • CSP is a browserside security layer, not a replacement for application security.
  • Static Next.js pages cannot use requestspecific nonce behaviour in the same way dynamic pages can.
  • Thirdparty scripts and tag managers should be inventoried before writing policy.
  • Broad allowances such as unsafeinline should be treated as debt, not default architecture.
  • CSP rollout should be incremental, monitored, and owned.

Relevant Services

  • Next.js Platform Architecture

    Clarify Next.js platform architecture when tenancy, shared systems, App Router behaviour, or team boundaries are slowing delivery down.

  • Next.js Platform Consulting

    Senior Next.js architecture work for legacy platforms, difficult migrations, and live stacks that need clearer delivery direction before more work piles on.

  • Next.js Site Broke After Deploy

    Stabilise a Next.js production incident after deploy when the app works locally but the live site is now broken, inconsistent, or only failing against production conditions.