Skip to content

feat(nextjs): Isolate nonce fetch in Suspense boundary for PPR support#7773

Open
jacekradko wants to merge 7 commits intomainfrom
jacek/user-4607-do-not-await-nonce-in-the-clerkprovider
Open

feat(nextjs): Isolate nonce fetch in Suspense boundary for PPR support#7773
jacekradko wants to merge 7 commits intomainfrom
jacek/user-4607-do-not-await-nonce-in-the-clerkprovider

Conversation

@jacekradko
Copy link
Member

@jacekradko jacekradko commented Feb 5, 2026

Summary

  • Move nonce fetching from the server ClerkProvider's main body into a separate DynamicClerkScripts server component wrapped in Suspense
  • This allows pages using dynamic=true to remain statically renderable and compatible with PPR/cacheComponents

Problem

In the Next.js App Router server ClerkProvider, we await nonce from headers via getNonceHeaders(). This calls headers() which opts the entire page out of static rendering and breaks PPR/cacheComponents.

Solution

Isolate the nonce fetch in a Suspense boundary:

  • Create DynamicClerkScripts async server component that fetches nonce and renders scripts
  • Add getNonce cached function to utils
  • When dynamic=true, render <Suspense><DynamicClerkScripts/></Suspense>
  • Skip client ClerkScripts when server scripts are used via __internal_skipScripts prop

Test plan

  • Verify build passes
  • Test with dynamic=true - scripts should render correctly
  • Test PPR behavior - page should remain statically renderable with dynamic scripts isolated
  • Test CSP nonce functionality still works

Closes USER-4607

Summary by CodeRabbit

  • New Features

    • Dynamic server-side script loading with deferred rendering and optional UI preloading for faster startup.
    • New option to skip injecting client scripts when desired.
  • Refactor

    • More reliable nonce retrieval and per-request caching to improve script loading under strict Content-Security-Policy scenarios.

Move nonce fetching from the server ClerkProvider's main body into a
separate DynamicClerkScripts server component wrapped in Suspense.
This allows pages using dynamic=true to remain statically renderable
and compatible with PPR/cacheComponents.

- Create DynamicClerkScripts async server component
- Add getNonce cached function to utils
- Skip client ClerkScripts when server scripts are used
- Pass __internal_skipScripts through KeylessProvider
@vercel
Copy link

vercel bot commented Feb 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
clerk-js-sandbox Skipped Skipped Feb 6, 2026 4:13am

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Feb 5, 2026

⚠️ No Changeset found

Latest commit: 5913ae9

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

📝 Walkthrough

Walkthrough

Adds an internal boolean prop __internal_skipScripts to NextClerkProvider props and threads it through KeylessProvider, server ClerkProvider, and client ClerkProvider to optionally skip injecting client scripts. Moves nonce retrieval into a new async server component DynamicClerkScripts, rendered inside a React Suspense boundary, and adds a cached getNonce helper. Introduces a shared ClerkScriptTags component for rendering script/link tags and simplifies ClerkScript(s) rendering logic.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: isolating nonce fetching within a Suspense boundary to preserve PPR support, which is the core objective of this PR.
Linked Issues check ✅ Passed All objectives from USER-4607 are met: nonce fetching moved from main ClerkProvider body, wrapped in Suspense boundary, cached getNonce utility added, and __internal_skipScripts flag prevents duplicate script rendering.
Out of Scope Changes check ✅ Passed All changes are directly aligned with USER-4607 requirements; no unrelated modifications detected. ClerkScriptTags refactoring and router simplification directly support the main objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 5, 2026

Open in StackBlitz

@clerk/agent-toolkit

npm i https://pkg.pr.new/@clerk/agent-toolkit@7773

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@7773

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@7773

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@7773

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@7773

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@7773

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@7773

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@7773

@clerk/express

npm i https://pkg.pr.new/@clerk/express@7773

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@7773

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@7773

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@7773

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@7773

@clerk/react

npm i https://pkg.pr.new/@clerk/react@7773

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@7773

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@7773

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@7773

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@7773

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@7773

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@7773

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@7773

commit: 5913ae9

@jacekradko jacekradko requested a review from Ephem February 5, 2026 18:38
…ndling

Extract duplicated script rendering into a shared ClerkScriptTags component
used by both ClerkScripts (client) and DynamicClerkScripts (server). Add
try/catch to getNonce() so errors in prerendering or "use cache" contexts
degrade gracefully instead of propagating unhandled.
…n RSC

Import clerkJSScriptUrl, buildClerkJSScriptAttributes, clerkUIScriptUrl
from @clerk/shared/loadClerkJsScript instead of @clerk/react/internal in
the shared ClerkScriptTags component. The @clerk/react/internal barrel
re-exports modules that use React.createContext, which breaks when the
RSC bundler evaluates the barrel in server component context.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant