Next.js CSP: Nonces, Hashes and Static Pages


The choice between using nonces and hashes

Summary verdict upfront: Getting a truly strict Content Security Policy (CSP) to play nicely with Next.js (especially with Static Site Generation / SSG) remains frustrating, brittle, and not worth the overhead for the vast majority of projects. Unless you are prepared to enforce nonces via Server-Side Rendering (SSR) and give up ordinary full-page caching, you are probably better off sticking with the standard static headers from Next.js docs.

I wasted quite a few hours today trying to get strict CSPs working correctly with Next.js.

My verdict: just don’t. It can work, but with SSG it doesn’t work very well (for now, at least).
To be clear, some weak CSP is better than none at all, and it does also depend on the libraries you use. But you’re more likely than not going to allow “unsafe-inline” styles and scripts (which are the ones you really shouldn’t allow for good security). So I’d just recommend all the Next.js devs out there to go with the default static example from their docs and call it a day. Not worth the headache unless you really absolutely need it and are willing to pay the price.

A bit of background info

Disclaimer: I’m not an expert in this. This is just what I learned by trying to implement it, researching it and discussing this topic with AI.

Setting strict Content Security Policies (CSPs) can greatly reduce the possible damage of XSS attacks. That’s basically their main purpose. In other words, CSPs are a tool we can use to stop ourselves from accidentally shooting us in the foot.

By not implementing CSPs, you’re not inherently exposing yourself to XSS attacks, it’s just that if your website has such a vulnerability, the possible damage is a lot greater.


Now, a strict CSP (reminder: strict = good) typically avoids unsafe-inline and instead uses nonces or hashes for the inline code you intentionally want to keep:

  • Nonces: the server attaches a random nonce to every request for a website. This is probably the easier way, but it requires SSR. Because every request must contain a new nonce, ordinary full-page caching is no longer an option. That does not automatically rule out cached data or static assets. To me, that is a bit unnecessary when the whole page would be static otherwise (remember, in serverless environments we are often billed per request)

  • Hashes: the other possibility is to allow the checksums of your intended inline code in the CSP. In principle, that works well with SSG. Next.js also documents experimental Subresource Integrity (SRI) for the App Router: external scripts get an integrity attribute at build time. That verifies their contents, but does not automatically handle CSP allowances for every inline script and style. So it is not a ready-made solution for every project.

Which option makes sense? According to the current Next.js docs, the nonce route requires dynamic rendering and rules out SSG and ISR for those pages. Hashes are generally a better fit for static pages, but experimental SRI support is limited to the App Router and scripts generated at build time. The details depend on your Next.js version, router and build setup (my experience above is not a test of every combination).

An ignored problem

The thing is, CSPs are not exactly new. Pretty much all major browsers support CSPs since 2011. However, I believe that most web devs either don’t know about CSPs or just don’t care (remember, it is possible build secure websites without CSPs).

The result is that pretty much all major js frameworks have no, or often very lackluster CSP implementations. Next.js for example still marks SRI support as experimental. Astros support for CSPs was a bit better when I tried it, but was also still experimental at the time.

Documentation around CSPs is usually pretty lackluster as well, no matter which js framework you look at.

A solution?

Well, I have to disappoint you here. I don’t really have one. The sad fact is, we are stuck between a rock and a hard place here. The support is just not there yet, and just not using your favorite frameworks is hardly a good solution.

For Next.js devs: You can either choose the nonce route and give up ordinary full-page caching, or implement hash-based allowances which probably will take quite some time (yes, there are some libraries that will help you, but there is no ready made solution like we web developers have often come to expect).

Or you go try out Astro, but that is also not perfect, and after using both Next.js and Astro I think I like Astro better.

Search this website