Next.js 实现「受保护路由(Protected Routes)」
前置核心说明
Next.js 中实现受保护路由 和 React + react-router-dom 的实现逻辑完全不同,核心原因是:
- Next.js 是自带路由系统的全栈框架,不需要安装
react-router-dom,路由基于「文件系统」自动生成,这是 Next.js 路由的核心特性; - Next.js 分为两个主流大版本,路由实现方案完全不同,是两个独立的体系,需要分开学习:
- ✅ Next.js 12 及更早版本:基于「Pages Router」(页面路由),绝大多数老项目/教程都是这个版本;
- ✅ Next.js 13/14 最新版本:基于「App Router」(应用路由),采用 App 目录,是官方主推的新版路由方案; 两个版本的受保护路由实现方式无任何通用代码,下文会分别讲解两种方案,均为项目生产级写法,可直接复用。
一、Next.js 受保护路由的核心概念
和 React 一致,受保护路由的作用是:限制指定页面的访问权限,只有满足鉴权条件(最常见:用户已登录、拥有指定角色),才能访问页面内容;未满足条件时,自动重定向到登录页/无权限页,禁止访问。 常见场景:个人中心、订单页面、后台管理面板、付费内容页等,都需要做路由级别的鉴权保护。
Next.js 鉴权的核心判断依据:
- 前端层面:从
localStorage/cookies获取用户登录标识(token)、用户角色(role); - 服务端层面:Next.js 支持服务端鉴权,这是比 React 纯前端鉴权更安全的核心优势,可防止前端篡改本地数据绕过权限校验。
二、方案一:Next.js 12 及更早版本 (Pages Router 页面路由,最常用)
✔️ 核心前提(必看)
- 该版本的路由基于
pages目录,文件即路由:pages/index.js→/、pages/dashboard.js→/dashboard、pages/profile/settings.js→/profile/settings; - 该版本实现受保护路由的核心核心:利用 Next.js 的
getServerSideProps或getStaticProps这两个「数据获取函数」,在页面组件渲染之前执行鉴权逻辑; - 优先级:推荐使用
getServerSideProps做受保护路由鉴权,原因:getServerSideProps是服务端执行的函数,每次请求页面都会运行,鉴权逻辑在服务端完成,前端无法篡改,安全性极高;getStaticProps是构建时执行,生成静态页面,适合无权限的公开页面,不适合需要实时鉴权的受保护页面;
- 核心API:
context.res.writeHead(302, { Location: '/login' }).end()—— Next.js 服务端重定向的核心写法。
版本12 实现受保护路由的 2 种主流写法
写法1:内联鉴权(适合单个页面,简单快捷)
直接在需要保护的页面文件中,添加 getServerSideProps 函数,在函数内部写鉴权逻辑,这是最基础的写法。
适用场景:项目中受保护页面较少的情况。
// pages/dashboard.js 【受保护页面:必须登录才能访问】
export default function Dashboard() {
return (
<div>
<h1>后台仪表盘</h1>
<p>该页面仅登录用户可见</p>
</div>
)
}
// 核心:页面渲染前 服务端执行的鉴权函数
export async function getServerSideProps(context) {
// 1. 从请求头/ cookies 中获取用户登录凭证(token)【服务端安全获取】
// 推荐用 cookies 存储token,比 localStorage 更安全,服务端可直接读取
const token = context.req.cookies.token || null;
// 2. 核心鉴权逻辑:无token = 未登录
if (!token) {
// 3. 未登录 → 服务端重定向到登录页,禁止访问当前页面
return {
redirect: {
destination: '/login', // 重定向目标页面
permanent: false, // 是否永久重定向(固定写false即可)
},
};
}
// 可选:拓展 - 角色权限校验(管理员才能访问)
const userRole = context.req.cookies.role;
if (userRole !== 'admin') {
return {
redirect: {
destination: '/no-permission',
permanent: false,
},
};
}
// 4. 鉴权通过 → 正常渲染页面,可传递数据给组件
return { props: { userRole } };
}
写法2:封装通用鉴权高阶函数(推荐,全局复用,项目必用)
如果项目中有多个受保护页面,每个页面都写一遍 getServerSideProps 会造成大量代码冗余,最佳实践是:封装一个通用的鉴权高阶函数,所有受保护页面直接复用即可,一次封装,全局使用。
步骤1:封装通用鉴权函数(新建文件)
新建 utils/withAuth.js ,封装核心鉴权逻辑,支持基础登录校验 + 角色权限校验:
// utils/withAuth.js 通用鉴权高阶函数
export function withAuth(gsspFunc, requiredRole = null) {
return async (context) => {
// 1. 基础登录校验:获取token
const token = context.req.cookies.token || null;
if (!token) {
return {
redirect: { destination: '/login', permanent: false },
};
}
// 2. 可选:角色权限校验(如果传入了requiredRole)
if (requiredRole) {
const userRole = context.req.cookies.role;
if (userRole !== requiredRole) {
return {
redirect: { destination: '/no-permission', permanent: false },
};
}
}
// 3. 鉴权通过:如果当前页面有自己的getServerSideProps,执行并返回数据
if (gsspFunc) {
return await gsspFunc(context);
}
// 4. 鉴权通过:无额外数据,正常渲染页面
return { props: {} };
};
}
步骤2:在任意受保护页面复用(一行代码搞定)
所有需要保护的页面,直接导入封装的 withAuth 函数,替换原有的 getServerSideProps 即可,极大简化代码:
// pages/profile.js 受保护页面-普通用户可访问
import { withAuth } from '../utils/withAuth';
export default function Profile() {
return <h1>个人中心 - 仅登录用户可见</h1>;
}
// 核心:复用鉴权函数,只做【登录校验】
export const getServerSideProps = withAuth();
// pages/admin.js 受保护页面-仅管理员可访问
import { withAuth } from '../utils/withAuth';
export default function Admin() {
return <h1>管理员后台 - 仅管理员可见</h1>;
}
// 核心:复用鉴权函数,做【登录校验+角色校验】
export const getServerSideProps = withAuth(null, 'admin');
// pages/order.js 受保护页面-有自己的业务数据逻辑
import { withAuth } from '../utils/withAuth';
export default function Order({ orders }) {
return <div>我的订单:{JSON.stringify(orders)}</div>;
}
// 核心:复用鉴权 + 保留自己的业务数据逻辑
const getOrders = async (context) => {
const orders = [{ id: 1, name: '订单1' }]; // 模拟请求数据库
return { props: { orders } };
};
export const getServerSideProps = withAuth(getOrders);
三、方案二:Next.js 13/14 最新版本 (App Router 应用路由,官方主推)
✔️ 核心前提(必看,与Pages Router 完全不同)
- 该版本的路由基于
app目录(替代原有的pages目录),是 Next.js 最新的路由体系,采用「React 服务器组件(RSC)」为默认组件; - 无
getServerSideProps/getStaticProps:新版 App Router 彻底移除了这两个函数,数据获取和鉴权逻辑写法全部更新; - 核心组件/API:
middleware.js(全局中间件),这是 Next.js 13/14 实现受保护路由的最优方案、官方推荐方案; - 核心优势:一个文件,全局鉴权,所有路由的权限控制都在一个文件中完成,无需在每个页面写鉴权逻辑,维护成本极低,是生产项目的首选。
版本13/14 核心特性:middleware.js 全局中间件
什么是 middleware.js
middleware.js是 Next.js 13+ 的全局路由中间件,文件必须放在项目的「根目录」(和 app/pages 同级),文件名固定为 middleware.js,框架会自动识别并执行;- 执行时机:在用户的请求到达目标页面之前 执行,相当于所有路由的「统一拦截器」;
- 执行环境:同时支持服务端+客户端,鉴权逻辑在服务端执行,安全性拉满;
- 核心能力:拦截请求 → 校验权限 → 允许访问 / 重定向,完美契合受保护路由的需求。
版本13/14 实现受保护路由(生产级最优写法,推荐)
步骤1:创建全局中间件文件
在项目根目录新建文件:middleware.js (文件名不可修改),这是所有鉴权逻辑的入口,核心代码如下:
// middleware.js 【根目录,全局路由中间件】
import { NextResponse } from 'next/server';
import { cookies } from 'next/headers';
// 核心:配置【需要保护的路由列表】
const protectedRoutes = ['/dashboard', '/profile', '/admin', '/orders'];
// 核心:配置【公开路由列表】(无需鉴权,所有人可访问)
const publicRoutes = ['/', '/login', '/register', '/about'];
export function middleware(request) {
// 1. 获取当前访问的路由路径
const path = request.nextUrl.pathname;
// 2. 判断当前路径是否是受保护路由/公开路由
const isProtectedRoute = protectedRoutes.includes(path);
const isPublicRoute = publicRoutes.includes(path);
// 3. 服务端安全获取用户登录凭证(token/role)
const cookieStore = cookies();
const token = cookieStore.get('token')?.value; // 从cookies获取token
const userRole = cookieStore.get('role')?.value; // 从cookies获取角色
// 4. 核心鉴权逻辑 - 分场景处理
// 场景1:访问受保护路由 + 未登录(无token) → 重定向到登录页
if (isProtectedRoute && !token) {
return NextResponse.redirect(new URL('/login', request.url));
}
// 场景2:访问受保护路由 + 已登录 + 角色权限校验(仅/admin需要管理员)
if (path === '/admin' && userRole !== 'admin') {
return NextResponse.redirect(new URL('/no-permission', request.url));
}
// 场景3:已登录状态下,访问登录页 → 重定向到首页(避免重复登录)
if (isPublicRoute && path === '/login' && token) {
return NextResponse.redirect(new URL('/', request.url));
}
// 场景4:所有校验通过 → 允许访问,放行请求
return NextResponse.next();
}
// 关键配置:指定中间件生效的路由范围(必写)
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};
步骤2:config.matcher 配置说明
上述代码最后导出的 config.matcher 是核心配置,作用是:指定哪些路由需要经过中间件拦截,这个写法是官方推荐的「通用写法」,含义是:
- 拦截所有路由,排除 接口请求(
/api)、静态资源(_next/static)、图片资源(_next/image)、网站图标(favicon.ico); - 目的:避免中间件拦截非页面请求,提升性能,无需修改,直接复制即可。
步骤3:页面文件创建
在 app 目录下创建页面文件,所有页面都不需要写任何鉴权逻辑,所有权限控制都由 middleware.js 完成,极致简洁:
// 项目目录结构
├── app/
│ ├── page.js → 首页 / (公开)
│ ├── login/page.js → 登录页 /login (公开)
│ ├── dashboard/page.js → 仪表盘 /dashboard (受保护)
│ ├── profile/page.js → 个人中心 /profile (受保护)
│ ├── admin/page.js → 管理员后台 /admin (受保护+角色校验)
│ └── no-permission/page.js → 无权限页 /no-permission (公开)
├── middleware.js → 全局鉴权中间件(根目录)
所有页面组件只需要写业务逻辑即可,例如:
// app/dashboard/page.js 受保护页面,无任何鉴权代码
export default function Dashboard() {
return <h1>后台仪表盘 - 已登录用户可见</h1>;
}
四、Next.js 登录/退出的配套逻辑
不管是 Pages Router 还是 App Router,都需要配套的「登录写入凭证」「退出清除凭证」逻辑,才能让受保护路由生效,这里补充通用的生产级写法:
登录逻辑(写入 Token/Role 到 Cookies)
推荐用 cookies 存储登录凭证,而非 localStorage,原因:cookies 可以在服务端直接读取,localStorage 只能在客户端读取,服务端鉴权必须用 cookies。
// 登录页面的登录按钮点击事件(通用)
const handleLogin = async () => {
// 1. 模拟请求后端接口,获取token和role
const res = await fetch('/api/login', { method: 'POST', body: { username, password } });
const { token, role } = await res.json();
// 2. 写入cookies(Next.js 提供的便捷方式,客户端/服务端均可)
document.cookie = `token=${token}; path=/; max-age=86400`; // 有效期1天
document.cookie = `role=${role}; path=/; max-age=86400`;
// 3. 登录成功,重定向到首页/目标页
window.location.href = '/';
};
退出逻辑(清除 Cookies)
const handleLogout = () => {
// 清除登录凭证
document.cookie = 'token=; path=/; max-age=0';
document.cookie = 'role=; path=/; max-age=0';
// 重定向到登录页
window.location.href = '/login';
};
五、Next.js 受保护路由 核心注意事项
- 前端鉴权 ≠ 后端鉴权:和 React 一样,Next.js 的路由鉴权只是「前端拦截」,所有敏感的后端接口(/api)必须单独做服务端鉴权,校验请求头/ cookies 中的 token,防止用户通过接口直接访问数据;
- 优先用 Cookies 存储凭证:不要用
localStorage,localStorage仅在客户端生效,服务端的鉴权逻辑(getServerSideProps/middleware)无法读取,这是新手最容易踩的坑; - Pages Router 选型:如果用 Pages Router,必须用 getServerSideProps,不要用 useEffect 在组件内做前端鉴权,会出现「页面一闪而过」的问题(组件先渲染再重定向),体验极差;
- App Router 选型:如果用 App Router,唯一推荐 middleware.js,这是官方最优解,比在组件内写鉴权逻辑高效、安全、易维护;
- 永久重定向问题:所有重定向的
permanent属性都写false,true会被浏览器缓存重定向规则,导致开发/生产环境的路由异常。
六、总结
Next.js 12 (Pages Router)
- 路由基于 pages 目录,文件即路由;
- 核心实现:
getServerSideProps服务端函数,在页面渲染前执行鉴权; - 最佳实践:封装
withAuth高阶函数,全局复用鉴权逻辑; - 优势:服务端鉴权,安全无篡改,兼容老项目。
Next.js 13/14 (App Router)
- 路由基于 app 目录,React 服务器组件为默认;
- 核心实现:根目录
middleware.js全局中间件,统一拦截所有路由请求; - 最佳实践:一个文件管控所有路由权限,页面无冗余鉴权代码;
- 优势:极致简洁、全局统一、性能最优,官方主推。
通用核心
- Next.js 受保护路由的核心:在页面渲染/请求到达前完成鉴权拦截;
- 存储凭证首选
cookies,服务端可读取,安全性远高于 localStorage; - 所有重定向均为临时重定向(permanent: false);
- 前端路由鉴权必须配合后端接口鉴权,双重保障数据安全。