11import { NextRequest } from 'next/server' ;
22
3- // Pre-compiled regex patterns for error detection
4- const USE_CACHE_PATTERN = / u s e c a c h e | c a c h e s c o p e / i;
5- const DYNAMIC_CACHE_PATTERN = / d y n a m i c d a t a s o u r c e / i;
6- // note: new error message syntax introduced in next@14.1.1-canary.21
7- // but we still want to support older versions.
8- // https://github.com/vercel/next.js/pull/61332 (dynamic-rendering.ts:153)
3+ const CLERK_USE_CACHE_MARKER = Symbol . for ( 'clerk_use_cache_error' ) ;
4+
5+ /**
6+ * Custom error class for "use cache" errors with a symbol marker to prevent double-wrapping.
7+ */
8+ export class ClerkUseCacheError extends Error {
9+ readonly [ CLERK_USE_CACHE_MARKER ] = true ;
10+
11+ constructor ( message : string , public readonly originalError ?: Error ) {
12+ super ( message ) ;
13+ this . name = 'ClerkUseCacheError' ;
14+ }
15+ }
16+
17+ export const isClerkUseCacheError = ( e : unknown ) : e is ClerkUseCacheError => {
18+ return e instanceof Error && CLERK_USE_CACHE_MARKER in e ;
19+ } ;
20+
21+ // Patterns for Next.js "use cache" errors - tightened to reduce false positives
22+ const USE_CACHE_WITH_DYNAMIC_API_PATTERN =
23+ / i n s i d e \s + " u s e c a c h e " | " u s e c a c h e " .* (?: h e a d e r s | c o o k i e s ) | (?: h e a d e r s | c o o k i e s ) .* " u s e c a c h e " / i;
24+ const CACHE_SCOPE_PATTERN = / c a c h e s c o p e / i;
25+ const DYNAMIC_DATA_SOURCE_PATTERN = / d y n a m i c d a t a s o u r c e / i;
26+ // https://github.com/vercel/next.js/pull/61332
927const ROUTE_BAILOUT_PATTERN = / R o u t e .* ? n e e d s t o b a i l o u t o f p r e r e n d e r i n g a t t h i s p o i n t b e c a u s e i t u s e d .* ?./ ;
1028
1129export const isPrerenderingBailout = ( e : unknown ) => {
@@ -14,24 +32,18 @@ export const isPrerenderingBailout = (e: unknown) => {
1432 }
1533
1634 const { message } = e ;
17-
1835 const lowerCaseInput = message . toLowerCase ( ) ;
19- const dynamicServerUsage = lowerCaseInput . includes ( 'dynamic server usage' ) ;
20- const bailOutPrerendering = lowerCaseInput . includes ( 'this page needs to bail out of prerendering' ) ;
21-
22- // Next.js 16+ with cacheComponents: headers() rejects during prerendering
23- // Error: "During prerendering, `headers()` rejects when the prerender is complete"
24- const headersRejectsDuringPrerendering = lowerCaseInput . includes ( 'during prerendering' ) ;
2536
2637 return (
27- ROUTE_BAILOUT_PATTERN . test ( message ) || dynamicServerUsage || bailOutPrerendering || headersRejectsDuringPrerendering
38+ ROUTE_BAILOUT_PATTERN . test ( message ) ||
39+ lowerCaseInput . includes ( 'dynamic server usage' ) ||
40+ lowerCaseInput . includes ( 'this page needs to bail out of prerendering' ) ||
41+ lowerCaseInput . includes ( 'during prerendering' )
2842 ) ;
2943} ;
3044
3145/**
32- * Detects if the error is from using dynamic APIs inside a "use cache" component.
33- * Next.js 16+ throws specific errors when headers(), cookies(), or other dynamic
34- * APIs are accessed inside a cache scope.
46+ * Detects Next.js errors from using dynamic APIs (headers/cookies) inside "use cache".
3547 */
3648export const isNextjsUseCacheError = ( e : unknown ) : boolean => {
3749 if ( ! ( e instanceof Error ) ) {
@@ -40,19 +52,19 @@ export const isNextjsUseCacheError = (e: unknown): boolean => {
4052
4153 const { message } = e ;
4254
43- // Check for "use cache" or "cache scope" mentions
44- if ( USE_CACHE_PATTERN . test ( message ) ) {
55+ // "use cache" with dynamic API context (e.g., 'used `headers()` inside "use cache"')
56+ if ( USE_CACHE_WITH_DYNAMIC_API_PATTERN . test ( message ) ) {
57+ return true ;
58+ }
59+
60+ // "cache scope" with dynamic data source (e.g., 'Dynamic data sources inside a cache scope')
61+ if ( CACHE_SCOPE_PATTERN . test ( message ) && DYNAMIC_DATA_SOURCE_PATTERN . test ( message ) ) {
4562 return true ;
4663 }
4764
48- // Check compound pattern: requires both "dynamic data source" AND "cache"
49- return DYNAMIC_CACHE_PATTERN . test ( message ) && message . toLowerCase ( ) . includes ( 'cache' ) ;
65+ return false ;
5066} ;
5167
52- /**
53- * Error message for when auth()/currentUser() is called inside a "use cache" function.
54- * Exported so it can be reused in auth.ts and currentUser.ts for consistent messaging.
55- */
5668export const USE_CACHE_ERROR_MESSAGE =
5769 `Clerk: auth() and currentUser() cannot be called inside a "use cache" function. ` +
5870 `These functions access \`headers()\` internally, which is a dynamic API not allowed in cached contexts.\n\n` +
@@ -71,21 +83,18 @@ export const USE_CACHE_ERROR_MESSAGE =
7183
7284export async function buildRequestLike ( ) : Promise < NextRequest > {
7385 try {
74- // Dynamically import next/headers, otherwise Next12 apps will break
75- // @ts -expect-error: Cannot find module 'next/headers' or its corresponding type declarations.ts(2307)
86+ // @ts -expect-error - Dynamically import to avoid breaking Next 12 apps
7687 const { headers } = await import ( 'next/headers' ) ;
7788 const resolvedHeaders = await headers ( ) ;
7889 return new NextRequest ( 'https://placeholder.com' , { headers : resolvedHeaders } ) ;
7990 } catch ( e : any ) {
80- // rethrow the error when react throws a prerendering bailout
8191 // https://nextjs.org/docs/messages/ppr-caught-error
8292 if ( e && isPrerenderingBailout ( e ) ) {
8393 throw e ;
8494 }
8595
86- // Provide a helpful error message for "use cache" components
8796 if ( e && isNextjsUseCacheError ( e ) ) {
88- throw new Error ( `${ USE_CACHE_ERROR_MESSAGE } \n\nOriginal error: ${ e . message } ` ) ;
97+ throw new ClerkUseCacheError ( `${ USE_CACHE_ERROR_MESSAGE } \n\nOriginal error: ${ e . message } ` , e ) ;
8998 }
9099
91100 throw new Error (
0 commit comments