Skip to content
Open
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/nextjs-cache-components-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/nextjs': patch
---

Add support for Next.js 16 cache components by improving error detection and providing helpful error messages when `auth()` or `currentUser()` are called inside a `"use cache"` function.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { auth } from '@clerk/nextjs/server';

// This function deliberately calls auth() inside "use cache" to trigger the error
async function getCachedAuthData() {
'use cache';
// This WILL throw an error because auth() uses headers() internally
const { userId } = await auth();
return { userId };
}

export async function GET() {
try {
const data = await getCachedAuthData();
return Response.json(data);
} catch (e: any) {
// Return the error message so we can verify it in tests
return Response.json({ error: e.message }, { status: 500 });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Suspense } from 'react';
import { currentUser, clerkClient } from '@clerk/nextjs/server';

// Simulated cached operation that fetches additional user data
async function getCachedUserProfile(userId: string) {
'use cache';
// This is the CORRECT pattern:
// - currentUser() is called OUTSIDE the cache function
// - Only the userId is passed into the cache function
// - The cache function uses clerkClient() which is allowed in cache contexts
const client = await clerkClient();
const user = await client.users.getUser(userId);

return {
userId,
cachedAt: new Date().toISOString(),
profile: {
fullName: [user.firstName, user.lastName].filter(Boolean).join(' ') || 'Unknown',
emailCount: user.emailAddresses?.length ?? 0,
},
};
}

async function CurrentUserCacheContent() {
// Step 1: Call currentUser() OUTSIDE the cache function
const user = await currentUser();

if (!user) {
return (
<>
<p>Please sign in to test the caching pattern with currentUser().</p>
<div data-testid='signed-out'>Not signed in</div>
</>
);
}

// Step 2: Pass userId INTO the cache function
const cachedProfile = await getCachedUserProfile(user.id);

return (
<>
<p>
This demonstrates the correct way to use <code>&quot;use cache&quot;</code> with currentUser():
</p>
<ol>
<li>
Call <code>currentUser()</code> <strong>outside</strong> the cache function
</li>
<li>
Pass the <code>userId</code> <strong>into</strong> the cache function
</li>
<li>
Use <code>clerkClient()</code> inside the cache function (allowed)
</li>
</ol>

<div className='test-result success'>
<h3>Cached Profile Data:</h3>
<pre data-testid='cached-profile'>{JSON.stringify(cachedProfile, null, 2)}</pre>
</div>

<div data-testid='current-user-id'>{user.id}</div>

<pre>
{`
// Correct pattern:
const user = await currentUser(); // Outside cache
if (user) {
const profile = await getCachedProfile(user.id); // Pass userId in
}

async function getCachedProfile(userId: string) {
'use cache';
const client = await clerkClient();
return client.users.getUser(userId);
}
`}
</pre>
</>
);
}

export default function CurrentUserCacheCorrectPage() {
return (
<main>
<h1>currentUser() with &quot;use cache&quot; Correct Pattern</h1>

<Suspense fallback={<div>Loading...</div>}>
<CurrentUserCacheContent />
</Suspense>
</main>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Suspense } from 'react';
import { currentUser } from '@clerk/nextjs/server';

async function CurrentUserContent() {
const user = await currentUser();

return (
<>
<div className={`test-result ${user ? 'success' : ''}`}>
<h3>Current User Result:</h3>
<pre>
{JSON.stringify(
{
id: user?.id ?? null,
firstName: user?.firstName ?? null,
lastName: user?.lastName ?? null,
primaryEmailAddress: user?.primaryEmailAddress?.emailAddress ?? null,
isSignedIn: !!user,
},
null,
2,
)}
</pre>
</div>

<div data-testid='current-user-id'>{user?.id ?? 'Not signed in'}</div>
<div data-testid='current-user-email'>{user?.primaryEmailAddress?.emailAddress ?? 'No email'}</div>
</>
);
}

export default function CurrentUserServerComponentPage() {
return (
<main>
<h1>currentUser() in Server Component</h1>
<p>This page tests using currentUser() in a standard React Server Component.</p>

<Suspense fallback={<div>Loading user...</div>}>
<CurrentUserContent />
</Suspense>
</main>
);
}
13 changes: 11 additions & 2 deletions integration/templates/next-cache-components/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,26 @@ export default function Home() {
<li>
<Link href='/auth-server-component'>auth() in Server Component</Link>
</li>
<li>
<Link href='/current-user-server-component'>currentUser() in Server Component</Link>
</li>
<li>
<Link href='/auth-server-action'>auth() in Server Action</Link>
</li>
<li>
<Link href='/api/auth-check'>auth() in API Route</Link>
</li>
<li>
<Link href='/use-cache-error'>use cache with auth() (should error)</Link>
<Link href='/use-cache-error'>use cache with auth() (documentation)</Link>
</li>
<li>
<Link href='/use-cache-error-trigger'>use cache error trigger (actual error)</Link>
</li>
<li>
<Link href='/use-cache-correct'>&quot;use cache&quot; correct pattern (auth)</Link>
</li>
<li>
<Link href='/use-cache-correct'>&quot;use cache&quot; correct pattern</Link>
<Link href='/current-user-cache-correct'>&quot;use cache&quot; correct pattern (currentUser)</Link>
</li>
<li>
<Link href='/ppr-auth'>PPR with auth()</Link>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use client';

import { useEffect, useState } from 'react';

export default function UseCacheErrorTriggerPage() {
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);

const triggerError = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch('/api/use-cache-error-trigger');
const data = await response.json();
if (data.error) {
setError(data.error);
}
} catch (e: any) {
setError(e.message);
} finally {
setLoading(false);
}
};

useEffect(() => {
triggerError();
}, []);

return (
<main>
<h1>&quot;use cache&quot; Error Trigger</h1>
<p>This page triggers an actual error by calling auth() inside a &quot;use cache&quot; function.</p>

{loading && <div data-testid='loading'>Loading...</div>}

{error && (
<div
className='test-result error'
data-testid='error-message'
>
<h3>Error Caught:</h3>
<pre style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>{error}</pre>
</div>
)}

<button
onClick={triggerError}
data-testid='trigger-btn'
>
Trigger Error Again
</button>
</main>
);
}
Loading