Skip to content

Commit a0204a8

Browse files
jacekradkobrkalowpanteliselef
authored
feat(nextjs): Next.js@15 compatibility (clerk#4366)
Co-authored-by: Bryce Kalow <bryce@clerk.dev> Co-authored-by: panteliselef <pantelis@clerk.dev>
1 parent 8ddb3ea commit a0204a8

80 files changed

Lines changed: 3122 additions & 1987 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/gorgeous-suits-rush.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@clerk/nextjs": major
3+
---
4+
5+
Stop `<ClerkProvider>` from opting applications into dynamic rendering. A new prop, `<ClerkProvider dynamic>` can be used to opt-in to dynamic rendering and make auth data available during server-side rendering. The RSC `auth()` helper should be preferred for accessing auth data during dynamic rendering.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
---
2+
"@clerk/nextjs": major
3+
"@clerk/upgrade": minor
4+
---
5+
6+
@clerk/nextjs: Converting auth() and clerkClient() interfaces to be async
7+
@clerk/upgrade: Adding required codemod for @clerk/nextjs breaking changes
8+
9+
# Migration guide
10+
11+
## `auth()` is now async
12+
13+
Previously the `auth()` method from `@clerk/nextjs/server` was synchronous.
14+
15+
```typescript
16+
import { auth } from '@clerk/nextjs/server';
17+
18+
export function GET() {
19+
const { userId } = auth();
20+
return new Response(JSON.stringify({ userId }));
21+
}
22+
```
23+
24+
The `auth` method now becomes asynchronous. You will need to make the following changes to the snippet above to make it compatible.
25+
26+
```diff
27+
- export function GET() {
28+
+ export async function GET() {
29+
- const { userId } = auth();
30+
+ const { userId } = await auth();
31+
return new Response(JSON.stringify({ userId }));
32+
}
33+
```
34+
35+
## Clerk middleware auth is now async
36+
37+
```typescript
38+
import { clerkClient, clerkMiddleware } from '@clerk/nextjs/server';
39+
import { NextResponse } from 'next/server';
40+
41+
export default clerkMiddleware(async (auth, request) => {
42+
const resolvedAuth = await auth();
43+
44+
const count = await resolvedAuth.users.getCount();
45+
46+
if (count) {
47+
return NextResponse.redirect(new URL('/new-url', request.url));
48+
}
49+
});
50+
51+
export const config = {
52+
matcher: [...],
53+
};
54+
```
55+
56+
## clerkClient() is now async
57+
58+
Previously the `clerkClient()` method from `@clerk/nextjs/server` was synchronous.
59+
60+
```typescript
61+
import { clerkClient, clerkMiddleware } from '@clerk/nextjs/server';
62+
import { NextResponse } from 'next/server';
63+
64+
export default clerkMiddleware((auth, request) => {
65+
const client = clerkClient();
66+
67+
const count = await client.users?.getCount();
68+
69+
if (count) {
70+
return NextResponse.redirect(new URL('/new-url', request.url));
71+
}
72+
});
73+
74+
export const config = {
75+
matcher: [...],
76+
};
77+
```
78+
79+
The method now becomes async. You will need to make the following changes to the snippet above to make it compatible.
80+
81+
```diff
82+
- export default clerkMiddleware((auth, request) => {
83+
- const client = clerkClient();
84+
+ export default clerkMiddleware(async (auth, request) => {
85+
+ const client = await clerkClient();
86+
const count = await client.users?.getCount();
87+
88+
if (count) {
89+
}
90+
```

.changeset/shiny-numbers-walk.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@clerk/nextjs": major
3+
---
4+
5+
Support `unstable_rethrow` inside `clerkMiddleware`.
6+
We changed the errors thrown by `protect()` inside `clerkMiddleware` in order for `unstable_rethrow` to recognise them and rethrow them.

.changeset/ten-worms-report.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@clerk/nextjs": major
3+
---
4+
5+
Removes deprecated APIs: `authMiddleware()`, `redirectToSignIn()`, and `redirectToSignUp()`. See the migration guide to learn how to update your usage.

.changeset/two-bottles-report.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@clerk/clerk-react": minor
3+
---
4+
5+
Internal changes to support `<ClerkProvider dynamic>`

integration/presets/longRunningApps.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ export const createLongRunningApps = () => {
2323
{ id: 'react.vite.withEmailLinks', config: react.vite, env: envs.withEmailLinks },
2424
{ id: 'remix.node.withEmailCodes', config: remix.remixNode, env: envs.withEmailCodes },
2525
{ id: 'next.appRouter.withEmailCodes', config: next.appRouter, env: envs.withEmailCodes },
26+
{ id: 'next.appRouter.15RCwithEmailCodes', config: next.appRouter15Rc, env: envs.withEmailCodes },
2627
{
2728
id: 'next.appRouter.withEmailCodes_persist_client',
2829
config: next.appRouter,
2930
env: envs.withEmailCodes_destroy_client,
3031
},
3132
{ id: 'next.appRouter.withCustomRoles', config: next.appRouter, env: envs.withCustomRoles },
33+
{ id: 'next.appRouter.15RCwithCustomRoles', config: next.appRouter15Rc, env: envs.withCustomRoles },
3234
{ id: 'quickstart.next.appRouter', config: next.appRouterQuickstart, env: envs.withEmailCodesQuickstart },
3335
{ id: 'elements.next.appRouter', config: elements.nextAppRouter, env: envs.withEmailCodes },
3436
{ id: 'astro.node.withCustomRoles', config: astro.node, env: envs.withCustomRoles },

integration/presets/next.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,60 @@ const appRouter = applicationConfig()
1616
.addDependency('react-dom', constants.E2E_REACT_DOM_VERSION)
1717
.addDependency('@clerk/nextjs', constants.E2E_CLERK_VERSION || clerkNextjsLocal);
1818

19+
const appRouter15Rc = applicationConfig()
20+
.setName('next-app-router')
21+
.useTemplate(templates['next-app-router'])
22+
.setEnvFormatter('public', key => `NEXT_PUBLIC_${key}`)
23+
.addScript('setup', constants.E2E_NPM_FORCE ? 'npm i --force' : 'npm i')
24+
.addScript('dev', 'npm run dev')
25+
.addScript('build', 'npm run build')
26+
.addScript('serve', 'npm run start')
27+
.addDependency('next', 'rc')
28+
.addDependency('react', 'rc')
29+
.addDependency('react-dom', 'rc')
30+
.addDependency('@clerk/nextjs', constants.E2E_CLERK_VERSION || clerkNextjsLocal)
31+
.addFile(
32+
'src/middleware.ts',
33+
() => `import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
34+
import { unstable_rethrow } from 'next/navigation';
35+
36+
const csp = \`default-src 'self';
37+
script-src 'self' 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' 'nonce-deadbeef';
38+
img-src 'self' https://img.clerk.com;
39+
worker-src 'self' blob:;
40+
style-src 'self' 'unsafe-inline';
41+
frame-src 'self' https://challenges.cloudflare.com;
42+
\`;
43+
44+
const isProtectedRoute = createRouteMatcher(['/protected(.*)', '/user(.*)', '/switcher(.*)']);
45+
const isAdminRoute = createRouteMatcher(['/only-admin(.*)']);
46+
const isCSPRoute = createRouteMatcher(['/csp']);
47+
48+
export default clerkMiddleware(async (auth, req) => {
49+
if (isProtectedRoute(req)) {
50+
try { await auth.protect() }
51+
catch (e) { unstable_rethrow(e) }
52+
}
53+
54+
if (isAdminRoute(req)) {
55+
try { await auth.protect({role: 'admin'}) }
56+
catch (e) { unstable_rethrow(e) }
57+
}
58+
59+
if (isCSPRoute(req)) {
60+
req.headers.set('Content-Security-Policy', csp.replace(/\\n/g, ''));
61+
}
62+
});
63+
64+
export const config = {
65+
matcher: [
66+
'/((?!.*\\\\..*|_next).*)', // Don't run middleware on static files
67+
'/', // Run middleware on index page
68+
'/(api|trpc)(.*)',
69+
], // Run middleware on API routes
70+
};`,
71+
);
72+
1973
const appRouterTurbo = appRouter
2074
.clone()
2175
.setName('next-app-router-turbopack')
@@ -48,6 +102,7 @@ const appRouterAPWithClerkNextV4 = appRouterQuickstart
48102

49103
export const next = {
50104
appRouter,
105+
appRouter15Rc,
51106
appRouterTurbo,
52107
appRouterQuickstart,
53108
appRouterAPWithClerkNextLatest,
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { auth } from '@clerk/nextjs/server';
22

3-
export function GET() {
4-
const { userId } = auth();
3+
export async function GET() {
4+
const { userId } = await auth();
55
return new Response(JSON.stringify({ userId }));
66
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { auth } from '@clerk/nextjs/server';
22

3-
export function GET() {
4-
const { userId } = auth().protect(has => has({ role: 'admin' }) || has({ role: 'org:editor' }));
3+
export async function GET() {
4+
const { userId } = await auth.protect((has: any) => has({ role: 'admin' }) || has({ role: 'org:editor' }));
55
return new Response(JSON.stringify({ userId }));
66
}

integration/templates/next-app-router/src/app/csp/page.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { headers } from 'next/headers';
22
import { ClerkLoaded } from '@clerk/nextjs';
33

4-
export default function CSPPage() {
4+
export default async function CSPPage() {
5+
const cspHeader = await headers().get('Content-Security-Policy');
6+
57
return (
68
<div>
7-
CSP: <pre>{headers().get('Content-Security-Policy')}</pre>
9+
CSP: <pre>{cspHeader}</pre>
810
<ClerkLoaded>
911
<p>clerk loaded</p>
1012
</ClerkLoaded>

0 commit comments

Comments
 (0)