Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/brown-kids-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/nextjs': patch
---

Handle `dynamicIO` errors when request apis are accessed on prerender. This fixes issues with `ppr: true, dynamicIO: true` when using `<ClerkProvider dynamic/>`.
39 changes: 25 additions & 14 deletions packages/nextjs/src/app-router/server/ClerkProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { AuthObject } from '@clerk/backend';
import type { InitialState, Without } from '@clerk/types';
import { headers } from 'next/headers';
import React from 'react';
Expand Down Expand Up @@ -27,21 +26,33 @@ export async function ClerkProvider(
props: Without<NextClerkProviderProps, '__unstable_invokeMiddlewareOnAuthStateChange'>,
) {
const { children, dynamic, ...rest } = props;
let statePromise: Promise<null | AuthObject> = Promise.resolve(null);
let nonce = Promise.resolve('');

if (dynamic) {
async function generateStatePromise() {
if (!dynamic) {
return Promise.resolve(null);
}
if (isNext13) {
/**
* For some reason, Next 13 requires that functions which call `headers()` are awaited where they are invoked.
* Without the await here, Next will throw a DynamicServerError during build.
*/
return Promise.resolve(await getDynamicClerkState());
}
return getDynamicClerkState();
}

async function generateNonce() {
if (!dynamic) {
return Promise.resolve('');
}
if (isNext13) {
/**
* For some reason, Next 13 requires that functions which call `headers()` are awaited where they are invoked.
* Without the await here, Next will throw a DynamicServerError during build.
*/
statePromise = Promise.resolve(await getDynamicClerkState());
nonce = Promise.resolve(await getNonceFromCSPHeader());
} else {
statePromise = getDynamicClerkState();
nonce = getNonceFromCSPHeader();
return Promise.resolve(await getNonceFromCSPHeader());
}
return getNonceFromCSPHeader();
}

const propsWithEnvs = mergeNextClerkPropsWithEnv({
Expand All @@ -51,8 +62,8 @@ export async function ClerkProvider(
let output = (
<ClientClerkProvider
{...mergeNextClerkPropsWithEnv(rest)}
nonce={await nonce}
initialState={await statePromise}
nonce={await generateNonce()}
initialState={await generateStatePromise()}
>
{children}
</ClientClerkProvider>
Expand All @@ -75,8 +86,8 @@ export async function ClerkProvider(
__internal_claimKeylessApplicationUrl: newOrReadKeys.claimUrl,
__internal_copyInstanceKeysUrl: newOrReadKeys.apiKeysUrl,
})}
nonce={await nonce}
initialState={await statePromise}
nonce={await generateNonce()}
initialState={await generateStatePromise()}
>
{children}
</ClientClerkProvider>
Expand All @@ -88,7 +99,7 @@ export async function ClerkProvider(
if (dynamic) {
return (
// TODO: fix types so AuthObject is compatible with InitialState
<PromisifiedAuthProvider authPromise={statePromise as unknown as Promise<InitialState>}>
<PromisifiedAuthProvider authPromise={generateStatePromise() as unknown as Promise<InitialState>}>
{output}
</PromisifiedAuthProvider>
);
Expand Down
4 changes: 2 additions & 2 deletions packages/nextjs/src/app-router/server/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const isPrerenderingBailout = (e: unknown) => {
return routeRegex.test(message) || dynamicServerUsage || bailOutPrerendering;
};

export async function buildRequestLike() {
export async function buildRequestLike(): Promise<NextRequest> {
try {
// Dynamically import next/headers, otherwise Next12 apps will break
// @ts-expect-error: Cannot find module 'next/headers' or its corresponding type declarations.ts(2307)
Expand Down Expand Up @@ -67,7 +67,7 @@ export function getScriptNonceFromHeader(cspHeaderValue: string): string | undef
// Grab the nonce by trimming the 'nonce-' prefix.
?.slice(7, -1);

// If we could't find the nonce, then we're done.
// If we couldn't find the nonce, then we're done.
if (!nonce) {
return;
}
Expand Down
Loading