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/tangy-sides-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/nextjs': patch
---

Updates middleware location check to account for proxy.ts in next 16+ applications.
5 changes: 3 additions & 2 deletions packages/nextjs/src/app-router/server/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { unauthorized } from '../../server/nextErrors';
import type { AuthProtect } from '../../server/protect';
import { createProtect } from '../../server/protect';
import { decryptClerkRequestData } from '../../server/utils';
import { isNextWithUnstableServerActions } from '../../utils/sdk-versions';
import { isNext16OrHigher, isNextWithUnstableServerActions } from '../../utils/sdk-versions';
import { buildRequestLike } from './utils';

/**
Expand Down Expand Up @@ -81,7 +81,8 @@ export const auth: AuthFn = (async (options?: AuthOptions) => {

try {
const isSrcAppDir = await import('../../server/fs/middleware-location.js').then(m => m.hasSrcAppDir());
return [`Your Middleware exists at ./${isSrcAppDir ? 'src/' : ''}middleware.(ts|js)`];
const fileName = isNext16OrHigher ? 'middleware.(ts|js) or proxy.(ts|js)' : 'middleware.(ts|js)';
return [`Your Middleware exists at ./${isSrcAppDir ? 'src/' : ''}${fileName}`];
} catch {
return [];
}
Expand Down
16 changes: 12 additions & 4 deletions packages/nextjs/src/server/fs/middleware-location.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isNext16OrHigher } from '../../utils/sdk-versions';
import { nodeCwdOrThrow, nodeFsOrThrow, nodePathOrThrow } from './utils';

function hasSrcAppDir() {
Expand All @@ -12,12 +13,17 @@ function hasSrcAppDir() {

function suggestMiddlewareLocation() {
const fileExtensions = ['ts', 'js'] as const;
// Next.js 16+ supports both middleware.ts (Edge runtime) and proxy.ts (Node.js runtime)
const fileNames = isNext16OrHigher ? ['middleware', 'proxy'] : ['middleware'];
const fileNameDisplay = isNext16OrHigher ? 'middleware or proxy' : 'middleware';

const suggestionMessage = (
fileName: string,
extension: (typeof fileExtensions)[number],
to: 'src/' | '',
from: 'src/app/' | 'app/' | '',
) =>
`Clerk: clerkMiddleware() was not run, your middleware file might be misplaced. Move your middleware file to ./${to}middleware.${extension}. Currently located at ./${from}middleware.${extension}`;
`Clerk: clerkMiddleware() was not run, your ${fileNameDisplay} file might be misplaced. Move your ${fileNameDisplay} file to ./${to}${fileName}.${extension}. Currently located at ./${from}${fileName}.${extension}`;

const { existsSync } = nodeFsOrThrow();
const path = nodePathOrThrow();
Expand All @@ -31,9 +37,11 @@ function suggestMiddlewareLocation() {
to: 'src/' | '',
from: 'src/app/' | 'app/' | '',
): string | undefined => {
for (const fileExtension of fileExtensions) {
if (existsSync(path.join(basePath, `middleware.${fileExtension}`))) {
return suggestionMessage(fileExtension, to, from);
for (const fileName of fileNames) {
for (const fileExtension of fileExtensions) {
if (existsSync(path.join(basePath, `${fileName}.${fileExtension}`))) {
return suggestionMessage(fileName, fileExtension, to, from);
}
}
}
return undefined;
Expand Down
7 changes: 6 additions & 1 deletion packages/nextjs/src/utils/sdk-versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ const isNext13 = nextPkg.version.startsWith('13.');
*/
const isNextWithUnstableServerActions = isNext13 || nextPkg.version.startsWith('14.0');

export { isNext13, isNextWithUnstableServerActions };
/**
* Next.js 16+ renamed middleware.ts to proxy.ts
*/
const isNext16OrHigher = nextPkg.version.startsWith('16.') || parseInt(nextPkg.version.split('.')[0]) >= 16;
Comment on lines +11 to +14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Correct the misleading JSDoc comment.

The JSDoc states that Next.js 16+ "renamed" middleware.ts to proxy.ts, but this is contradicted by the implementation in middleware-location.ts (line 16), which explicitly states that both files are supported: "Next.js 16+ supports both middleware.ts (Edge runtime) and proxy.ts (Node.js runtime)". The code also checks for both filenames in the array ['middleware', 'proxy'].

Update the JSDoc to accurately reflect that both files are supported:

 /**
- * Next.js 16+ renamed middleware.ts to proxy.ts
+ * Next.js 16+ introduced proxy.ts (Node.js runtime) alongside middleware.ts (Edge runtime)
  */
 const isNext16OrHigher = nextPkg.version.startsWith('16.') || parseInt(nextPkg.version.split('.')[0]) >= 16;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Next.js 16+ renamed middleware.ts to proxy.ts
*/
const isNext16OrHigher = nextPkg.version.startsWith('16.') || parseInt(nextPkg.version.split('.')[0]) >= 16;
/**
* Next.js 16+ introduced proxy.ts (Node.js runtime) alongside middleware.ts (Edge runtime)
*/
const isNext16OrHigher = nextPkg.version.startsWith('16.') || parseInt(nextPkg.version.split('.')[0]) >= 16;
🤖 Prompt for AI Agents
In packages/nextjs/src/utils/sdk-versions.ts around lines 11 to 14, the JSDoc
incorrectly states that Next.js 16+ "renamed middleware.ts to proxy.ts"; update
the comment to accurately state that Next.js 16+ supports both middleware.ts
(Edge runtime) and proxy.ts (Node.js runtime) or that both filenames are
supported, so it aligns with middleware-location.ts and the code that checks
['middleware','proxy'].

Copy link
Member

@jacekradko jacekradko Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add tests for this boolean expression (it kind of feels like a function)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we can make it a bit more robust for the future:

function meetsNextMinimumVersion(nextPkg, minimumMajorVersion = 16) {
  if (!nextPkg?.version) {
    return false;
  }
  
  const majorVersion = parseInt(nextPkg.version.split('.')[0], 10);
  return !isNaN(majorVersion) && majorVersion >= minimumMajorVersion;
}

const isNext16OrHigher = meetsNextMinimumVersion(nextPkg, 16);


export { isNext13, isNextWithUnstableServerActions, isNext16OrHigher };
Loading