Skip to content

[6.x] Allow static cache JS to be externalized - #15342

Merged
jasonvarga merged 2 commits into
statamic:6.xfrom
ryanmitchell:feature/nocache-csp
Sep 18, 2026
Merged

jasonvarga merged 2 commits into
statamic:6.xfrom
ryanmitchell:feature/nocache-csp

Conversation

@ryanmitchell

@ryanmitchell ryanmitchell commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes #8802.

Background

Full-measure (file) static caching injects two small inline <script> blocks into every cached page — one to swap the placeholder CSRF token, one to hydrate nocache regions. A site sending Content-Security-Policy: script-src 'self' blocks inline scripts, so full static caching can't be used with that policy.

What this does

Adds a script_delivery option to config/static_caching.php:

'script_delivery' => env('STATAMIC_STATIC_CACHING_SCRIPT_DELIVERY', 'inline'),
  • inline (default) — unchanged behaviour, byte-identical output.
  • external — the snippets are served from same-origin routes and referenced with <script src="…">, which satisfies script-src 'self' with no nonce or hash.

The two routes (statamic.nocache.js, statamic.csrf.js, under the action-route prefix) are only registered when script_delivery is external. They respond with application/javascript and an ETag.

The existing StaticCache::nocacheJs() / csrfTokenJs() overrides still work, and the default snippets are unchanged.

Backwards compatibility

Default is inline; existing sites see identical output. No breaking changes.

@ryanmitchell ryanmitchell changed the title [6.x] Allow static cache CS to be externalized [6.x] Allow static cache JS to be externalized Sep 2, 2026
The views were publishable, so a site could freeze a copy of the CSRF and nocache JS and silently miss future fixes to it. The publish tag copies the whole directory, so someone who only wanted to change the script tag would have frozen both JS bodies too. The tag itself also shouldn't have been a view: it re-branched on inline vs external, a decision the config already makes, so a published copy could contradict the config and break the mode it wasn't trying to touch.

The nonce example that justified publishing doesn't hold either. These tags are injected into full measure responses only, and those get written to disk and replayed, so a per-request nonce would be frozen and reused for every visitor. External delivery is the answer for a strict policy, which is what this PR is for.

So FileCacher builds the tags itself and owns inline vs external in one place, and both replacers just ask it for one. The default JS stays in the heredocs it was already in, leaving FileCacher purely additive. StaticCache::csrfTokenJs() and nocacheJs() still replace the bodies outright.

Also reverts the FakesViews namespace hint fix, which was only needed because the replacers rendered a statamic:: view.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jasonvarga

Copy link
Copy Markdown
Member

I've pushed a change that drops the publishable views and the nonce example along with them.

The nonce wouldn't actually have worked. These tags only get injected into full-measure responses, so the nonce would be baked into the HTML file and handed to every visitor, and a constant nonce isn't worth much.

Which just means the external mode is the right call here, since script-src 'self' doesn't need anything per-request.

Thanks!

@jasonvarga
jasonvarga merged commit c83c5c2 into statamic:6.x Sep 18, 2026
62 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NoCacheReplacer and CSP headers

2 participants