Next.js 使用 Clerk 后仅在 Vercel 返回 404,如何排查?
结论:先确认 404 出现在哪个请求上
在拿到实际请求路径、匹配结果和运行时错误之前,还不能确定唯一原因。移除 middleware 后恢复访问,说明故障与认证处理链路有关,但不能直接认定 Clerk 导致页面路由丢失。
原代码有两处需要分别确认:
auth().protect()是否符合实际安装的@clerk/nextjs版本。- 出现故障的请求是否进入了保护分支。
/about、/products、/contact、/cart 已在公开列表中。对于这些原样路径,按照所示代码,不应该执行 protect()。因此,即使修正认证 API,也不能保证解释或解决这些公开页面的 404。
为什么构建成功,访问时仍然会失败?
构建日志列出页面,说明构建阶段识别到了路由。实际请求仍可能经过认证、重定向和页面内部逻辑,最终返回 404。
Clerk 的认证检查也可能返回 404。例如,用户已登录但未通过授权检查时,auth.protect() 可以返回 404;未登录的普通页面请求通常会跳转到登录页,非文档请求则有不同的响应规则。排查时需要区分浏览器最初请求的页面、后续重定向目标,以及 API 等其他请求。Clerk auth() 文档
不过,原代码没有显式传入权限条件,公开页面也应绕过保护分支,因此不能认定当前 404 是权限不足造成的。
先核对 Clerk API 版本
查看实际依赖版本:
npm ls next @clerk/nextjs
同时检查 Vercel 构建使用的提交、锁文件和安装日志,确认是否与本地一致。
Clerk 的 v6 升级指南说明,auth() 改为异步调用,保护接口由 auth().protect() 改为 auth.protect()。如果使用对应 API,应写为:
export default clerkMiddleware(async (auth, req) => {
if (!isPublicRoute(req)) {
await auth.protect();
}
});
这个修正适用于实际执行保护分支的请求。由于没有提供依赖版本,不能直接认定原写法一定错误,也不能认定它就是生产环境故障的根因。Clerk v6 升级指南
记录生产环境实际匹配的路径
如果当前 SDK 仍支持原有的 createRouteMatcher() 配置,可以临时在回调中记录路径和匹配结果。下面的示例使用 auth.protect() API:
export default clerkMiddleware(async (auth, req) => {
const isPublic = isPublicRoute(req);
console.info("[clerk-route]", {
pathname: req.nextUrl.pathname,
isPublic,
});
if (!isPublic) {
await auth.protect();
}
});
保留原有的 isPublicRoute 和 config 定义即可。然后在 Vercel 运行时日志中查看对应请求,不要只看构建日志。
- 如果
/about的记录为isPublic: true,继续检查 Clerk 自身的会话处理、重定向,以及页面或布局内的其他认证逻辑。 - 如果记录为
false,核对实际 pathname、部署代码,以及是否存在路径前缀、重写或尾斜杠差异,再按预期调整匹配规则。 - 如果完全没有对应日志,核对访问的部署地址、代码版本,以及 middleware 是否被加载。
也可以临时启用 Clerk 提供的 { debug: true } 调试选项,查看认证处理过程。排查结束后移除临时日志,不要记录 Cookie、令牌或密钥。Clerk middleware 调试说明
App Router 应如何配置?
文件位置与 Next.js 版本保持一致
项目使用 src/app,认证入口应放在 src 下,与 app 同级:
- Next.js 15 及以前:
src/middleware.js。 - Next.js 16 及以后:按 Proxy 文件约定使用
src/proxy.js。
TypeScript 项目对应使用 .ts。文件名应依据实际 Next.js 版本选择。Clerk App Router 配置说明
将会话初始化与资源保护分别配置
当前 Clerk 文档已将 createRouteMatcher() 标记为弃用,并建议在读取或修改数据的位置执行认证与授权检查。采用这一方式的项目,可以在入口处只初始化 Clerk:
import { clerkMiddleware } from "@clerk/nextjs/server";
export default clerkMiddleware();
export const config = {
matcher: [
"/((?!_next|.*\\..*).*)",
"/(api|trpc)(.*)",
],
};
这里保留了题目中跳过带点路径的规则,并补充了 API 匹配。它不会自动保护业务页面;现有受保护资源必须先补齐自身的认证检查,再移除原来的保护分支。如果业务路径本身包含点号,还需要调整这条跳过规则。Clerk middleware 配置与保护建议
下面是一个需要登录的页面示例,题目中的公开页面不一定需要增加此检查:
import { auth } from "@clerk/nextjs/server";
export default async function PrivatePage() {
await auth.protect();
return <main>需要登录后访问的内容</main>;
}
读取私有数据的函数、Route Handler 或 Server Action 也应执行相应检查;仅在某个页面上检查登录,不能替代其他数据入口的保护。Clerk 内容保护指南
最后核对生产环境差异
在浏览器 Network 面板中直接打开 /about,保留请求记录,确认首个响应和每次跳转的目标。如果最初返回重定向,最终在 /sign-in 或其他地址出现 404,应修复该目标地址,而不是修改 /about/page.js。
即使环境变量已设置,也需要核对当前部署实际使用的是哪组变量、Publishable Key 与 Secret Key 是否属于同一个 Clerk 实例,以及生产域名和登录地址配置是否一致。如果配置了站内登录路径,确认对应页面确实存在;把 /sign-in(.*) 写入公开列表并不会创建登录页。
本地可以依次运行生产构建和服务,比较是否能够复现:
npm run build
npm run start
如果本地生产模式正常而 Vercel 异常,重点比较部署版本、环境变量、域名和请求跳转;如果本地生产模式同样失败,则结合服务器日志继续检查认证分支及页面执行逻辑。
备注:内容仅供参考。
