创见博客
Next.js如何实现路由,如何实现受保护的路由(getServerSideProps/middleware)
七崽爱吃小饼干2026/01/13阅读 0专栏 React

Next.js 实现「受保护路由(Protected Routes)」

前置核心说明

Next.js 中实现受保护路由 和 React + react-router-dom 的实现逻辑完全不同,核心原因是:

  1. Next.js 是自带路由系统的全栈框架,不需要安装 react-router-dom,路由基于「文件系统」自动生成,这是 Next.js 路由的核心特性;
  2. 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 页面路由,最常用)

✔️ 核心前提(必看)

  1. 该版本的路由基于 pages 目录,文件即路由:pages/index.js → / 、pages/dashboard.js → /dashboard 、pages/profile/settings.js → /profile/settings;
  2. 该版本实现受保护路由的核心核心:利用 Next.js 的 getServerSideProps 或 getStaticProps 这两个「数据获取函数」,在页面组件渲染之前执行鉴权逻辑;
  3. 优先级:推荐使用 getServerSideProps 做受保护路由鉴权,原因:
    • getServerSideProps 是服务端执行的函数,每次请求页面都会运行,鉴权逻辑在服务端完成,前端无法篡改,安全性极高;
    • getStaticProps 是构建时执行,生成静态页面,适合无权限的公开页面,不适合需要实时鉴权的受保护页面;
  4. 核心API:context.res.writeHead(302, { Location: '/login' }).end() —— Next.js 服务端重定向的核心写法。

版本12 实现受保护路由的 2 种主流写法

写法1:内联鉴权(适合单个页面,简单快捷)

直接在需要保护的页面文件中,添加 getServerSideProps 函数,在函数内部写鉴权逻辑,这是最基础的写法。 适用场景:项目中受保护页面较少的情况。

jsx
// 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 ,封装核心鉴权逻辑,支持基础登录校验 + 角色权限校验:

javascript
// 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 即可,极大简化代码:

jsx
// pages/profile.js 受保护页面-普通用户可访问
import { withAuth } from '../utils/withAuth';

export default function Profile() {
  return <h1>个人中心 - 仅登录用户可见</h1>;
}

// 核心:复用鉴权函数,只做【登录校验】
export const getServerSideProps = withAuth();
jsx
// pages/admin.js 受保护页面-仅管理员可访问
import { withAuth } from '../utils/withAuth';

export default function Admin() {
  return <h1>管理员后台 - 仅管理员可见</h1>;
}

// 核心:复用鉴权函数,做【登录校验+角色校验】
export const getServerSideProps = withAuth(null, 'admin');
jsx
// 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 完全不同)

  1. 该版本的路由基于 app 目录(替代原有的pages目录),是 Next.js 最新的路由体系,采用「React 服务器组件(RSC)」为默认组件;
  2. 无 getServerSideProps / getStaticProps:新版 App Router 彻底移除了这两个函数,数据获取和鉴权逻辑写法全部更新;
  3. 核心组件/API:middleware.js(全局中间件),这是 Next.js 13/14 实现受保护路由的最优方案、官方推荐方案;
  4. 核心优势:一个文件,全局鉴权,所有路由的权限控制都在一个文件中完成,无需在每个页面写鉴权逻辑,维护成本极低,是生产项目的首选。

版本13/14 核心特性:middleware.js 全局中间件

什么是 middleware.js

  • middleware.js 是 Next.js 13+ 的全局路由中间件,文件必须放在项目的「根目录」(和 app/pages 同级),文件名固定为 middleware.js,框架会自动识别并执行;
  • 执行时机:在用户的请求到达目标页面之前 执行,相当于所有路由的「统一拦截器」;
  • 执行环境:同时支持服务端+客户端,鉴权逻辑在服务端执行,安全性拉满;
  • 核心能力:拦截请求 → 校验权限 → 允许访问 / 重定向,完美契合受保护路由的需求。

版本13/14 实现受保护路由(生产级最优写法,推荐)

步骤1:创建全局中间件文件

在项目根目录新建文件:middleware.js (文件名不可修改),这是所有鉴权逻辑的入口,核心代码如下:

javascript
// 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 → 全局鉴权中间件(根目录)

所有页面组件只需要写业务逻辑即可,例如:

jsx
// 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。

jsx
// 登录页面的登录按钮点击事件(通用)
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)

jsx
const handleLogout = () => {
  // 清除登录凭证
  document.cookie = 'token=; path=/; max-age=0';
  document.cookie = 'role=; path=/; max-age=0';
  // 重定向到登录页
  window.location.href = '/login';
};

五、Next.js 受保护路由 核心注意事项

  1. 前端鉴权 ≠ 后端鉴权:和 React 一样,Next.js 的路由鉴权只是「前端拦截」,所有敏感的后端接口(/api)必须单独做服务端鉴权,校验请求头/ cookies 中的 token,防止用户通过接口直接访问数据;
  2. 优先用 Cookies 存储凭证:不要用 localStorage,localStorage 仅在客户端生效,服务端的鉴权逻辑(getServerSideProps/middleware)无法读取,这是新手最容易踩的坑;
  3. Pages Router 选型:如果用 Pages Router,必须用 getServerSideProps,不要用 useEffect 在组件内做前端鉴权,会出现「页面一闪而过」的问题(组件先渲染再重定向),体验极差;
  4. App Router 选型:如果用 App Router,唯一推荐 middleware.js,这是官方最优解,比在组件内写鉴权逻辑高效、安全、易维护;
  5. 永久重定向问题:所有重定向的 permanent 属性都写 false,true 会被浏览器缓存重定向规则,导致开发/生产环境的路由异常。

六、总结

Next.js 12 (Pages Router)

  1. 路由基于 pages 目录,文件即路由;
  2. 核心实现:getServerSideProps 服务端函数,在页面渲染前执行鉴权;
  3. 最佳实践:封装 withAuth 高阶函数,全局复用鉴权逻辑;
  4. 优势:服务端鉴权,安全无篡改,兼容老项目。

Next.js 13/14 (App Router)

  1. 路由基于 app 目录,React 服务器组件为默认;
  2. 核心实现:根目录 middleware.js 全局中间件,统一拦截所有路由请求;
  3. 最佳实践:一个文件管控所有路由权限,页面无冗余鉴权代码;
  4. 优势:极致简洁、全局统一、性能最优,官方主推。

通用核心

  1. Next.js 受保护路由的核心:在页面渲染/请求到达前完成鉴权拦截;
  2. 存储凭证首选 cookies,服务端可读取,安全性远高于 localStorage;
  3. 所有重定向均为临时重定向(permanent: false);
  4. 前端路由鉴权必须配合后端接口鉴权,双重保障数据安全。
评论
0/100