fix(frontend): CSP nonce for JSON-LD structured-data script on profile pages - #1025
Merged
OlaGreat merged 1 commit intoAug 26, 2026
Conversation
…file pages middleware.ts tightened script-src to 'self', which silently strips the inline application/ld+json block rendered in profile/[username]/page.tsx, dropping ProfilePage/Person structured data used for SEO. Middleware now generates a per-request nonce, adds it to script-src, and forwards it via an x-csp-nonce request header; the profile page reads it and applies it to the script tag. Closes OlaGreat#952
|
@libby-coder Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
4 tasks
OlaGreat
pushed a commit
that referenced
this pull request
Aug 31, 2026
Closes #1031) #1031 asked for the same fix already applied to the JSON-LD script in #952/#1025: read the CSP nonce via headers() and pass it to the inline theme-init <script> in layout.tsx. That approach was deliberately reverted in 669bb39 ("repair the red CI pipeline") because nonces can't work with statically prerendered pages — their HTML has no per-request nonce to carry, and a nonce-source in script-src makes browsers ignore 'unsafe-inline', which blocked all client-side JS on every static route. Reintroducing that fix here would hit the same wall from the other side: layout.tsx is the root layout shared by every route, so calling headers() in it (a Dynamic API) would force the entire app into dynamic rendering to get a per-request nonce — a much larger regression than the one 669bb39 fixed, and one that would undo issue #1034's Lighthouse budget work by tanking performance on every previously-static page. Instead, move the theme-init script out of line into a static asset (frontend/public/theme-init.js) loaded via next/script's `beforeInteractive` strategy. A same-origin external script is covered by `script-src 'self'` alone — it needs neither 'unsafe-inline' nor a nonce — and `beforeInteractive` is next/script's documented mechanism for a script that must run before hydration/paint, giving the same anti-flash timing the inline version had without tripping the `@next/next/no-sync-scripts` lint rule a raw `<script src>` would. Verified `npm run build` still prerenders the same routes as static (○) after this change — no route was forced into dynamic rendering.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
middleware.tstightenedscript-srcto'self', which silently strips the inlineapplication/ld+jsonblock rendered infrontend/src/app/profile/[username]/page.tsx— dropping theProfilePage/Personstructured data added for SEO.Fix
middleware.tsnow generates a per-request nonce, adds'nonce-<value>'toscript-src, and forwards the nonce to Server Components via anx-csp-noncerequest header.next/headersand applies it as thenonceprop on the JSON-LD<script>tag.Closes #952
Closes #951 — theme-init inline script in
layout.tsxhas the same root cause and should reuse this same nonce mechanism (x-csp-nonce) once addressed.Closes #965 — stale "no auth token" snapshot test for
NotificationPreferences.Closes #949 —
Webhook.secretHashis still stored/used as plaintext; needs a real hashing + secret re-derivation design.Test plan
npx tsc --noEmit— no new type errors introduced (pre-existing unrelated errors in dashboard/test files untouched)npx eslinton both changed files — cleanview-sourceshows the JSON-LD<script>with anonceattribute matching the CSP header, and that it's no longer blocked in the browser console