Skip to content

Commit 41f6515

Browse files
authored
feat(nextjs): Drop support for next v13 and v14 (#7197)
1 parent 15e5bf0 commit 41f6515

File tree

15 files changed

+528
-221
lines changed

15 files changed

+528
-221
lines changed

.changeset/ten-wolves-attack.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+
Drop support for `next@13` and `next@14` since they have reached [EOL](https://nextjs.org/support-policy#unsupported-versions). Now `>= [email protected]` is required.

.github/workflows/ci.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -296,9 +296,6 @@ jobs:
296296
]
297297
test-project: ["chrome"]
298298
include:
299-
- test-name: "nextjs"
300-
test-project: "chrome"
301-
next-version: "14"
302299
- test-name: "nextjs"
303300
test-project: "chrome"
304301
next-version: "15"

packages/nextjs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
### Prerequisites
3333

34-
- Next.js 13.5.7 or later
34+
- Next.js 15.2.3 or later
3535
- React 18 or later
3636
- Node.js `>=18.17.0` or later
3737
- An existing Clerk application. [Create your account for free](https://dashboard.clerk.com/sign-up?utm_source=github&utm_medium=clerk_nextjs).

packages/nextjs/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,10 @@
9292
},
9393
"devDependencies": {
9494
"crypto-es": "^2.1.0",
95-
"next": "14.2.33"
95+
"next": "15.2.3"
9696
},
9797
"peerDependencies": {
98-
"next": "^13.5.7 || ^14.2.25 || ^15.2.3 || ^16",
98+
"next": "^15.2.3 || ^16",
9999
"react": "catalog:peer-react",
100100
"react-dom": "catalog:peer-react"
101101
},

packages/nextjs/src/app-router/client/ClerkProvider.tsx

Lines changed: 5 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
'use client';
22
import { ClerkProvider as ReactClerkProvider } from '@clerk/react';
3-
import { inBrowser } from '@clerk/shared/browser';
4-
import { logger } from '@clerk/shared/logger';
53
import dynamic from 'next/dynamic';
64
import { useRouter } from 'next/navigation';
7-
import nextPackage from 'next/package.json';
8-
import React, { useEffect, useTransition } from 'react';
5+
import React from 'react';
96

107
import { useSafeLayoutEffect } from '../../client-boundary/hooks/useSafeLayoutEffect';
118
import { ClerkNextOptionsProvider, useClerkNextOptions } from '../../client-boundary/NextOptionsContext';
@@ -14,7 +11,6 @@ import { ClerkJSScript } from '../../utils/clerk-js-script';
1411
import { canUseKeyless } from '../../utils/feature-flags';
1512
import { mergeNextClerkPropsWithEnv } from '../../utils/mergeNextClerkPropsWithEnv';
1613
import { RouterTelemetry } from '../../utils/router-telemetry';
17-
import { isNextWithUnstableServerActions } from '../../utils/sdk-versions';
1814
import { detectKeylessEnvDriftAction } from '../keyless-actions';
1915
import { invalidateCacheAction } from '../server-actions';
2016
import { useAwaitablePush } from './useAwaitablePush';
@@ -29,20 +25,10 @@ const LazyCreateKeylessApplication = dynamic(() =>
2925
);
3026

3127
const NextClientClerkProvider = (props: NextClerkProviderProps) => {
32-
if (isNextWithUnstableServerActions) {
33-
const deprecationWarning = `Clerk:\nYour current Next.js version (${nextPackage.version}) will be deprecated in the next major release of "@clerk/nextjs". Please upgrade to [email protected] or later.`;
34-
if (inBrowser()) {
35-
logger.warnOnce(deprecationWarning);
36-
} else {
37-
logger.logOnce(`\n\x1b[43m----------\n${deprecationWarning}\n----------\x1b[0m\n`);
38-
}
39-
}
40-
4128
const { __unstable_invokeMiddlewareOnAuthStateChange = true, children } = props;
4229
const router = useRouter();
4330
const push = useAwaitablePush();
4431
const replace = useAwaitableReplace();
45-
const [isPending, startTransition] = useTransition();
4632

4733
// Call drift detection on mount (client-side)
4834
useSafeLayoutEffect(() => {
@@ -57,19 +43,13 @@ const NextClientClerkProvider = (props: NextClerkProviderProps) => {
5743
return props.children;
5844
}
5945

60-
useEffect(() => {
61-
if (!isPending) {
62-
window.__clerk_internal_invalidateCachePromise?.();
63-
}
64-
}, [isPending]);
65-
6646
useSafeLayoutEffect(() => {
6747
window.__unstable__onBeforeSetActive = intent => {
6848
/**
6949
* We need to invalidate the cache in case the user is navigating to a page that
7050
* was previously cached using the auth state that was active at the time.
7151
*
72-
* We also need to await for the invalidation to happen before we navigate,
52+
* We also need to await for the invalidation to happen before we navigate,
7353
* otherwise the navigation will use the cached page.
7454
*
7555
* For example, if we did not invalidate the flow, the following scenario would be broken:
@@ -85,25 +65,16 @@ const NextClientClerkProvider = (props: NextClerkProviderProps) => {
8565
* https://nextjs.org/docs/app/building-your-application/caching#invalidation-1
8666
*/
8767
return new Promise(resolve => {
88-
window.__clerk_internal_invalidateCachePromise = resolve;
89-
9068
const nextVersion = window?.next?.version || '';
9169

92-
// ATTENTION: Avoid using wrapping code with `startTransition` on versions >= 14
93-
// otherwise the fetcher of `useReverification()` will be pending indefinitely when called within `startTransition`.
94-
if (nextVersion.startsWith('13')) {
95-
startTransition(() => {
96-
router.refresh();
97-
});
98-
}
99-
// On Next.js v15 calling a server action that returns a 404 error when deployed on Vercel is prohibited, failing with 405 status code.
100-
// When a user transitions from "signed in" to "singed out", we clear the `__session` cookie, then we call `__unstable__onBeforeSetActive`.
70+
// On Next.js 15+ calling a server action that returns a 404 error when deployed on Vercel is prohibited, failing with 405 status code.
71+
// When a user transitions from "signed in" to "signed out", we clear the `__session` cookie, then we call `__unstable__onBeforeSetActive`.
10172
// If we were to call `invalidateCacheAction` while the user is already signed out (deleted cookie), any page protected by `auth.protect()`
10273
// will result to the server action returning a 404 error (this happens because server actions inherit the protection rules of the page they are called from).
10374
// SOLUTION:
10475
// To mitigate this, since the router cache on version 15 is much less aggressive, we can treat this as a noop and simply resolve the promise.
10576
// Once `setActive` performs the navigation, `__unstable__onAfterSetActive` will kick in and perform a router.refresh ensuring shared layouts will also update with the correct authentication context.
106-
else if (nextVersion.startsWith('15') && intent === 'sign-out') {
77+
if (nextVersion.startsWith('15') && intent === 'sign-out') {
10778
resolve(); // noop
10879
} else {
10980
void invalidateCacheAction().then(() => resolve());

packages/nextjs/src/app-router/client/useInternalNavFun.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,8 @@ export const useInternalNavFun = (props: {
3333
// as this is the way to perform a shallow navigation in Next.js App Router
3434
// without unmounting/remounting the page or fetching data from the server.
3535
if (opts?.__internal_metadata?.navigationType === 'internal') {
36-
// In 14.1.0, useSearchParams becomes reactive to shallow updates,
37-
// but only if passing `null` as the history state.
38-
// Older versions need to maintain the history state for push/replace to work,
39-
// without affecting how the Next router works.
40-
const state = ((window as any).next?.version ?? '') < '14.1.0' ? history.state : null;
41-
windowNav(state, '', to);
36+
// Passing `null` ensures App Router shallow navigations keep search params reactive.
37+
windowNav(null, '', to);
4238
} else {
4339
// If the navigation is external (usually when navigating away from the component but still within the app),
4440
// we should use the Next.js router to navigate as it will handle updating the URL and also

packages/nextjs/src/app-router/server/ClerkProvider.tsx

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { PromisifiedAuthProvider } from '../../client-boundary/PromisifiedAuthPr
77
import { getDynamicAuthData } from '../../server/buildClerkProps';
88
import type { NextClerkProviderProps } from '../../types';
99
import { mergeNextClerkPropsWithEnv } from '../../utils/mergeNextClerkPropsWithEnv';
10-
import { isNext13 } from '../../utils/sdk-versions';
1110
import { ClientClerkProvider } from '../client/ClerkProvider';
1211
import { getKeylessStatus, KeylessProvider } from './keyless-provider';
1312
import { buildRequestLike, getScriptNonceFromHeader } from './utils';
@@ -37,27 +36,13 @@ export async function ClerkProvider(
3736
if (!dynamic) {
3837
return Promise.resolve(null);
3938
}
40-
if (isNext13) {
41-
/**
42-
* For some reason, Next 13 requires that functions which call `headers()` are awaited where they are invoked.
43-
* Without the await here, Next will throw a DynamicServerError during build.
44-
*/
45-
return Promise.resolve(await getDynamicClerkState());
46-
}
4739
return getDynamicClerkState();
4840
}
4941

5042
async function generateNonce() {
5143
if (!dynamic) {
5244
return Promise.resolve('');
5345
}
54-
if (isNext13) {
55-
/**
56-
* For some reason, Next 13 requires that functions which call `headers()` are awaited where they are invoked.
57-
* Without the await here, Next will throw a DynamicServerError during build.
58-
*/
59-
return Promise.resolve(await getNonceHeaders());
60-
}
6146
return getNonceHeaders();
6247
}
6348

packages/nextjs/src/app-router/server/auth.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { unauthorized } from '../../server/nextErrors';
1111
import type { AuthProtect } from '../../server/protect';
1212
import { createProtect } from '../../server/protect';
1313
import { decryptClerkRequestData } from '../../server/utils';
14-
import { isNextWithUnstableServerActions } from '../../utils/sdk-versions';
1514
import { buildRequestLike } from './utils';
1615

1716
/**
@@ -75,10 +74,6 @@ export const auth: AuthFn = (async (options?: AuthOptions) => {
7574
const request = await buildRequestLike();
7675

7776
const stepsBasedOnSrcDirectory = async () => {
78-
if (isNextWithUnstableServerActions) {
79-
return [];
80-
}
81-
8277
try {
8378
const isSrcAppDir = await import('../../server/fs/middleware-location.js').then(m => m.hasSrcAppDir());
8479
return [`Your Middleware exists at ./${isSrcAppDir ? 'src/' : ''}middleware.(ts|js)`];

packages/nextjs/src/global.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ interface Window {
3535
promisesBuffer: Array<() => void> | undefined;
3636
}
3737
>;
38-
__clerk_internal_invalidateCachePromise: () => void | undefined;
3938
__clerk_nav_await: Array<(value: void) => void>;
4039
__clerk_nav: (to: string) => Promise<void>;
4140

packages/nextjs/src/server/createGetAuth.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import type { PendingSessionOptions } from '@clerk/shared/types';
44
import { isTruthy } from '@clerk/shared/underscore';
55

66
import { withLogger } from '../utils/debugLogger';
7-
import { isNextWithUnstableServerActions } from '../utils/sdk-versions';
87
import type { GetAuthDataFromRequestOptions } from './data/getAuthDataFromRequest';
98
import {
109
getAuthDataFromRequest as getAuthDataFromRequestOriginal,
@@ -37,11 +36,6 @@ export const createAsyncGetAuth = ({
3736
}
3837

3938
if (!detectClerkMiddleware(req)) {
40-
// Keep the same behaviour for versions that may have issues with bundling `node:fs`
41-
if (isNextWithUnstableServerActions) {
42-
assertAuthStatus(req, noAuthStatusMessage);
43-
}
44-
4539
const missConfiguredMiddlewareLocation = await import('./fs/middleware-location.js')
4640
.then(m => m.suggestMiddlewareLocation())
4741
.catch(() => undefined);

0 commit comments

Comments
 (0)