分类 前端 下的文章

如果希望在 Next.js 的服务端组件(Server Component)中获取当前请求的路径(pathname),但又不想使用 API 路由或客户端组件(Client Component),可以通过使用 Middleware 来实现。

使用 Middleware 获取当前路径

可以在 Next.js 的 Middleware 中拦截请求,并将当前路径添加到请求头中,然后在服务端组件中读取该请求头,从而获取当前路径。

1. 创建 Middleware

在项目的根目录下创建一个 middleware.ts 文件:

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-current-path', request.nextUrl.pathname);

  return NextResponse.next({
    request: {
      headers: requestHeaders,
    },
  });
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

上述 Middleware 会将当前请求的路径添加到请求头中的 x-current-path 字段。(Medium)

2. 在服务端组件中读取路径

在服务端组件中,可以使用 headers() 方法读取请求头,从而获取当前路径:(propelauth.com)

import { headers } from 'next/headers';
export default async function Getsuffix(layer : number):Promise<string>{
  const headerList = await headers();
  const pathname = headerList.get('x-current-path') || '/';
  // 解析路径后缀
  const segments = pathname.split('/');
  const titleSuffix = segments[layer] || '';
  return (
    titleSuffix
  );
}

在构建中大型的 Next.js 应用时,我们往往需要在请求进入应用之前做一些预处理,比如用户认证、国际化设置、重定向等。Next.js 从 12 版本开始支持 Middleware,它运行于边缘节点,可以在请求到达页面组件之前拦截和处理请求。

本文将逐步讲解如何在 Next.js 中实现多个中间件的逻辑,并且保持代码的清晰与模块化。


一、Middleware 的基础概念

在 Next.js 中,你只需要在项目根目录创建一个名为 middleware.tsmiddleware.js 的文件即可启用 Middleware。这个文件会自动被 Next.js 识别,不需要额外配置。

一个最基础的中间件看起来如下:

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  return NextResponse.next();
}

该中间件会对所有请求生效,并简单地允许请求继续往下走。


二、实现多个中间件逻辑的需求

假设我们现在有两个独立的功能需要处理:

  1. authMiddleware: 用于判断用户是否登录,未登录则重定向到 /login
  2. i18nMiddleware: 用于设置用户语言偏好

我们希望它们都在每个请求前依次执行。如何做到这点呢?


三、模块化中间件逻辑

为了保持代码整洁,我们将每个功能模块拆分到单独文件中:

middlewares/authMiddleware.ts

import { NextRequest, NextResponse } from 'next/server';

export function authMiddleware(request: NextRequest): NextResponse | void {
  const isAuthenticated = true; // 示例逻辑
  if (!isAuthenticated) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
}

middlewares/i18nMiddleware.ts

import { NextRequest, NextResponse } from 'next/server';

export function i18nMiddleware(request: NextRequest): NextResponse | void {
  const locale = request.headers.get('accept-language')?.split(',')[0] || 'en';
  request.headers.set('x-locale', locale);
}

四、在 middleware.ts 中组合这些中间件

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { authMiddleware } from './middlewares/authMiddleware';
import { i18nMiddleware } from './middlewares/i18nMiddleware';

export function middleware(request: NextRequest): NextResponse {
  const authResponse = authMiddleware(request);
  if (authResponse) return authResponse;

  const i18nResponse = i18nMiddleware(request);
  if (i18nResponse) return i18nResponse;

  return NextResponse.next();
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

我们依次执行每一个中间件函数:

  • 如果某个中间件返回了 NextResponse,表示它希望拦截或终止请求流程,例如重定向;
  • 如果返回 undefined,表示它只是修改了请求状态或头部,不影响流程,继续执行下一个。

这种模式非常直观,也方便调试和扩展。


五、为什么使用 return 会中止后续中间件?

很多开发者初看可能会疑惑:

"既然我要执行多个中间件,为什么用 return 会跳过后面的逻辑?"

这是因为我们在实现“链式中间件”的处理逻辑——每个中间件有权决定是否终止请求流程。

举例来说,用户未认证时我们希望立即重定向到 /login,那么其他逻辑(比如设置语言、加载用户数据)就不需要再执行。

这种设计思路与很多后端框架如 Express.js 的中间件模型类似。


六、总结

Next.js 的 Middleware 是一个强大且灵活的工具,允许你在请求生命周期的早期阶段拦截和处理逻辑。

  • 你只需一个 middleware.ts 文件
  • 将每个中间件功能模块化,方便复用与测试
  • 在主中间件函数中按顺序调用它们,并根据是否返回 NextResponse 控制流程

这样的设计既保持了结构的清晰,又便于日后扩展。如果你在构建应用时需要实现用户权限、国际化、AB 测试等功能,Middleware 是一个值得深入使用的机制。

希望本文对你理解和使用 Next.js Middleware 有所帮助!

使用 Cookies 存储和获取 Access Token 和 Refresh Token 是一种相对安全的做法,尤其是当你设置了 HttpOnly 标志时,这样可以防止 JavaScript 访问这些 Cookies,从而降低 XSS 攻击的风险。以下是如何在 React 中存储和获取 Cookies 的示例。

1. 安装 js-cookie

为了更方便地处理 Cookies,可以使用 js-cookie 库。首先,需要安装它:

npm install js-cookie

2. 示例代码

下面是如何使用 js-cookie 进行存储和获取 Cookies 的示例。

存储令牌

当你从登录 API 获取 Access Token 和 Refresh Token 后,可以使用 Cookies.set 来存储它们。

import Cookies from 'js-cookie';

// 假设你在登录成功时得到了这两个令牌
const accessToken = 'your_access_token';
const refreshToken = 'your_refresh_token';

// 存储 Cookies
Cookies.set('accessToken', accessToken, { expires: 7, secure: true, sameSite: 'Strict' }); // 7天过期
Cookies.set('refreshToken', refreshToken, { expires: 30, secure: true, sameSite: 'Strict' }); // 30天过期

在上面的代码中,expires 参数设置了 Cookies 的过期时间,secure 参数表示只有在 HTTPS 协议下才能发送 Cookies,sameSite 限制了 Cookies 在一定情况下的使用。

获取令牌

在需要访问令牌时,可以通过 Cookies.get 进行获取。

import Cookies from 'js-cookie';

// 获取令牌
const accessToken = Cookies.get('accessToken');
const refreshToken = Cookies.get('refreshToken');

// 使用令牌
if (accessToken) {
    console.log('Access Token:', accessToken);
}
if (refreshToken) {
    console.log('Refresh Token:', refreshToken);
}

3. 应用 Cookies 的情况

当你在发起API请求时,需要在请求头中附带 Access Token,可以通过上述方式获取它:

import axios from 'axios';
import Cookies from 'js-cookie';

// 创建一个 Axios 实例
const api = axios.create({
    baseURL: '<http://your-api-endpoint.com>',
});

// 添加请求拦截器
api.interceptors.request.use(
    config => {
        const token = Cookies.get('accessToken');
        if (token) {
            config.headers.Authorization = `Bearer ${token}`;
        }
        return config;
    },
    error => {
        return Promise.reject(error);
    }
);

// 示例 API 调用
const fetchData = async () => {
    try {
        const response = await api.get('/protected-resource');
        console.log(response.data);
    } catch (error) {
        console.error(error);
    }
};

// 调用 fetchData
fetchData();

4. 注意事项

  • HttpOnly Cookies:如果你将 Cookies 设置为 HttpOnly,JavaScript 将无法访问这些 Cookies,因此你需要在服务器端设置 Cookies,并通过 set-cookie 响应头发送给客户端。
  • Cross-Origin Resource Sharing (CORS):如果你的 API 在不同的域上,要确保 CORS 设置正确,以允许 cookies 传递。
  • Secure Flag:在生产环境中部署时,请确保你的应用使用 HTTPS,并且在存储 Cookies 时设置 secure 标志,使 Cookies 只能通过安全的连接传输。

通过这种方式,可以安全地存储和获取 Access Token 和 Refresh Token, 提高应用的安全性。

在 React Bootstrap 中,spanorderoffset 这三个参数用于定义网格布局中的列(Col)的行为:

  1. span: 这个参数指定了列应该占用的模板列的数量。例如,span: 6 意味着该列将在一个12列的网格中占用6列。这样可以灵活地控制列的宽度和布局。
  2. order: 这个参数用于控制内容的视觉顺序。通过设置该参数,可以改变列在页面上显示的顺序,而不仅仅是它们在代码中的顺序。例如,order: 1order: 2 可以改变两个列的排列顺序,即使它们在代码中声明的顺序是相反的。
  3. offset: 这个参数用于生成列的偏移量,即在某一列前留出的空白列数。例如,offset: 2 将在该列前面留出2列的空白,允许创建更加灵活的布局。

useLocation 用法:

  • useLocation 提供当前路由的完整路径(例如:/admin/create)。
  • 我们通过location.pathname.split('/')将路径拆分,将第一级子路径用于动态后缀更新。

例:

import React from 'react';
import {useLocation} from 'react-router-dom';

export default function App () {
  const location = useLocation(); // 获取当前路径
  let titleSuffix = ""; // 初始化动态后缀为空

  // 动态解析路径后缀,只取整个路径中的第一级
  if (location.pathname !== "/") {
    titleSuffix = location.pathname.split('/')[1];
  }

  return titleSuffix 
}