Next.js 如何返回 401 与 403 并自定义 unauthorized 和 forbidden 页面
2026/9/12 4:00:19 网站建设 项目流程

Next.js 如何返回 401 与 403 并自定义 unauthorized 和 forbidden 页面

【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js

在 App Router 应用里,你经常需要区分两种请求:未登录用户访问受保护页面时应返回401,已登录但权限不足的用户访问时应返回403,并且两种情况都要展示自己设计的提示页,而不是框架默认内容。Next.js 提供了unauthorizedforbidden两个函数,配合unauthorized.js/forbidden.js特殊文件来完成这件事。

先说清楚适用前提:这套能力在文档中标记为实验性功能。升级指南将forbiddenunauthorizedforbidden.jsunauthorized.jsauthInterrupts列在 "Features available in canary" 之下,且必须在next.config.js中显式开启authInterrupts才能使用,next/navigation导入不会自动生效。

开启 authInterrupts 开关

这是使用这两个函数的必做步骤。在next.config.js中启用authInterrupts

module.exports = { experimental: { authInterrupts: true, }, }

TypeScript 项目使用next.config.ts时:

import type { NextConfig } from 'next' const nextConfig: NextConfig = { experimental: { authInterrupts: true, }, } export default nextConfig

在渲染路径中调用 unauthorized 与 forbidden

unauthorized用于"未登录"场景,调用后会抛出NEXT_HTTP_ERROR_FALLBACK;401错误并终止当前路由片段的渲染,Next.js 同时注入<meta name="robots" content="noindex" />防止该页被索引;forbidden用于"已登录但权限不足"场景,抛出NEXT_HTTP_ERROR_FALLBACK;403错误,其余行为相同。两者都可以在 Server Components、Server Functions(Server Actions)和 Route Handlers 中调用,但不能在 root layout 中调用。

以 Server Component 为例,未登录返回 401:

import { verifySession } from '@/app/lib/dal' import { unauthorized } from 'next/navigation' export default async function DashboardPage() { const session = await verifySession() if (!session) { unauthorized() } return <div>Dashboard</div> }

角色检查返回 403:

import { verifySession } from '@/app/lib/dal' import { forbidden } from 'next/navigation' export default async function AdminPage() { const session = await verifySession() // Check if the user has the 'admin' role if (session.role !== 'admin') { forbidden() } return ( <main> <h1>Admin Dashboard</h1> <p>Welcome, {session.user.name}!</p> </main> ) }

示例中的verifySession(来自@/app/lib/dal)和db(来自@/app/lib/db)是文档示例中的会话校验与数据库辅助模块,请替换为你自己的会话校验与数据访问代码,函数签名按你的实现为准。

这两个函数的工作方式是抛出异常,TypeScript 返回类型为never,所以不需要写return unauthorized(),调用后执行即停止。在 Route Handler 中保护接口端点:

import { NextRequest, NextResponse } from 'next/server' import { verifySession } from '@/app/lib/dal' import { unauthorized } from 'next/navigation' export async function GET(req: NextRequest): Promise<NextResponse> { const session = await verifySession() if (!session) { unauthorized() } // Fetch data // ... }

在 Server Action 中保护敏感变更操作同理,例如只允许 admin 更新角色:

'use server' import { verifySession } from '@/app/lib/dal' import { forbidden } from 'next/navigation' export async function updateRole(formData: FormData) { const session = await verifySession() if (session.role !== 'admin') { forbidden() } // Perform the role update for authorized users // ... }

自定义 unauthorized 与 forbidden 页面

自定义 UI 通过unauthorized.js/forbidden.js特殊文件完成,这两个文件不接收任何 props。文档示例同时提供.tsx.js版本,下文以.tsx为例。

app目录根部放置全局文件,作为所有 401 场景的兜底 UI:

import Login from '@/app/components/Login' export default function Unauthorized() { return ( <main> <h1>401 - Unauthorized</h1> <p>Please log in to access this page.</p> <Login /> </main> ) }
import Link from 'next/link' export default function Forbidden() { return ( <div> <h2>Forbidden</h2> <p>You are not authorized to access this resource.</p> <Link href="/">Return Home</Link> </div> ) }

其中<Login />指向文档示例中的登录组件@/app/components/Login,替换为你项目自己的登录 UI 组件。

也可以为单个路由就近放置文件来局部覆盖 UI。例如把权限检查放在<Suspense>边界内的数据加载函数中,就在该路由旁添加unauthorized.tsx

import Link from 'next/link' export default function Unauthorized() { return ( <main> <h1>401 - Unauthorized</h1> <p> Please <Link href="/login">sign in</Link> to view your account. </p> </main> ) }

抛出异常后,它会传播到最近的unauthorized/forbidden边界并渲染对应文件。

验证结果

按文档描述,配置正确时可以得到以下可核对的结果:

  • 状态码unauthorized.js文件约定下 Next.js 返回401状态码,forbidden.js文件约定下返回403状态码;
  • 页面内容:请求未登录或权限不足时,用户看到的是你自定义的unauthorized/forbidden页面,而不是受保护的内容;
  • 反爬取标记:响应页面中会注入<meta name="robots" content="noindex" />
  • 失败信号:如果异常没有被框架接住(见下一节的未 await 场景),开发环境的服务端会打印⨯ unhandledRejection: NEXT_HTTP_ERROR_FALLBACK;401forbidden对应403),且页面不会渲染任何 unauthorized / forbidden UI——看到这个日志说明调用位置有问题。

排查与限制

检查位置必须在渲染路径上,且必须被 await。由于这两个函数靠抛异常工作,只有写在组件内部、或组件await的函数内部才会触发边界渲染。把unauthorized()放进一个未被 await 的 promise,异常会抛到无人捕获的地方,401 UI 不渲染,开发环境只剩unhandledRejection日志。文档给出的模式是把校验放在数据访问层函数里并保证await

import { Suspense } from 'react' import { verifySession } from '@/app/lib/dal' import { unauthorized } from 'next/navigation' async function getAccount() { const session = await verifySession() if (!session) { unauthorized() } return db.accounts.findByUserId(session.userId) } async function AccountDetails() { const account = await getAccount() return <p>Signed in as {account.email}</p> } export default function AccountPage() { return ( <main> <h1>Account</h1> <Suspense fallback={<p>Loading...</p>}> <AccountDetails /> </Suspense> </main> ) }

try/catch 会吞掉中断。如果调用点被包在try/catch里,异常会被你捕获,401/403 UI 不再渲染。需要让框架异常穿透时,在 catch 块开头调用unstable_rethrow(err)将其重新抛出(参考 unstable_rethrow 文档)。

流式响应开始后状态码无法再变。如果检查发生在<Suspense>边界内,响应可能已以200开始流式传输,此时状态码不再能改成 401/403,用户仍会看到自定义的 unauthorized / forbidden UI。要返回真实的 401/403 状态码,检查必须发生在响应开始流式传输之前;在使用 Cache Components 时,动态路由会先流式输出静态 shell,文档建议在proxy中执行该检查。

root layout 不可调用unauthorized/forbidden

版本说明unauthorizedforbidden与对应的特殊文件均自v15.1.0引入,且当前需配合实验性authInterrupts配置使用,接入生产前建议按 升级指南确认你所用 Next.js 版本包含这些 canary 特性。

更多细节可参考:unauthorized 函数、forbidden 函数、unauthorized.js 文件约定、forbidden.js 文件约定、authInterrupts 配置。

【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询