Skip to main content

重定向

在 Next.js 中有几种方式可以处理重定向。本页会介绍每个可选项、用例和如何管理大量重定向。

APIPurposeWhereStatus Code
redirect在突变或事件后重定向用户服务器组件,服务器操作,路由处理程序307 (暂时的) 或 303 (服务器操作)
permanentRedirect在突变或事件后重定向用户服务器组件,服务器操作,路由处理程序308 (永久的)
useRouter执行客户端导航客户端组件中的事件处理程序N/A
redirects in next.config.js根据路径重定向传入请求next.config.js file307 (暂时的) or 308 (永久的)
NextResponse.redirect根据状态重定向传入请求MiddlewareAny

redirect function

The redirect function 让您将用户重定向到其它 URL。您能够在服务器组件, 路由处理器, 和 服务器操作中调用redirect

经常在突变或事件后使用redirect。如:创建帖子:

"use server";

import { redirect } from "next/navigation";
import { revalidatePath } from "next/cache";

export async function createPost(id: string) {
try {
// Call database
} catch (error) {
// Handle errors
}

revalidatePath("/posts"); // Update cached posts
redirect(`/post/${id}`); // Navigate to the new post page
}
"use server";

import { redirect } from "next/navigation";
import { revalidatePath } from "next/cache";

export async function createPost(id) {
try {
// Call database
} catch (error) {
// Handle errors
}

revalidatePath("/posts"); // Update cached posts
redirect(`/post/${id}`); // Navigate to the new post page
}

您需要知道:

  • redirect 在默认情况下会返回一个 307(暂时重定向)状态码。当在服务器操作中使用时, 会返回 303 (请参阅其它)状态码, 其通常作为 POST 请求重定向到成功页的结果。
  • redirect 内部抛出错误,因此要在try/catch外调用它。
  • redirect可能会在渲染过程中而不是事件处理程序期间在客户端组件中调用。您可以使用useRouter hook 代替.
  • redirect 也接受绝对路径,并且重定向到外部链接。
  • 如果您想在渲染程序之前重定向, 使用 next.config.jsMiddleware.

参阅 redirect API reference 获取更多信息。

permanentRedirect function

The permanentRedirect function 让您将用户永久重定向到其它 URL。你能够在服务器组件, 路由处理程序, 和 服务器操作中调用permanentRedirect

permanentRedirect常常在改变了实体的规范 URL 的突变或事件后使用,如: 在用户更改了用户名后更新用户的个人资料 URL:

"use server";

import { permanentRedirect } from "next/navigation";
import { revalidateTag } from "next/cache";

export async function updateUsername(username: string, formData: FormData) {
try {
// Call database
} catch (error) {
// Handle errors
}

revalidateTag("username"); // Update all references to the username
permanentRedirect(`/profile/${username}`); // Navigate to the new user profile
}
"use server";

import { permanentRedirect } from "next/navigation";
import { revalidateTag } from "next/cache";

export async function updateUsername(username, formData) {
try {
// Call database
} catch (error) {
// Handle errors
}

revalidateTag("username"); // Update all references to the username
permanentRedirect(`/profile/${username}`); // Navigate to the new user profile
}

您需要知道:

  • permanentRedirect 在默认情况下返回 308 (永久重定向) 状态码。
  • permanentRedirect 也接受绝对路径和重定向到外部的链接。
  • 如果您想在渲染过程前重定向, 使用next.config.jsMiddleware.

参阅permanentRedirect API reference获取更多信息。

useRouter() hook

如果您需要在客户端组件中的事件处理程序内进行重定向,您能够使用useRouter hook 中的push 方法。例如:

"use client";

import { useRouter } from "next/navigation";

export default function Page() {
const router = useRouter();

return (
<button type="button" onClick={() => router.push("/dashboard")}>
Dashboard
</button>
);
}
"use client";

import { useRouter } from "next/navigation";

export default function Page() {
const router = useRouter();

return (
<button type="button" onClick={() => router.push("/dashboard")}>
Dashboard
</button>
);
}

Good to know:

  • 如果您不需要以编程方式导航用户, 您应该使用<Link>组件。

参阅useRouter API reference获取更多信息。

redirects in next.config.js

next.config.js 文件中的redirects选项允许您将传入的请求路径重定向到不同的地址路径。这在您改变页面的 URL 结构或提前知道重定向列表的时候极为有用。

redirects 支持 path, header, cookie, 和 query matching, 使您能够根据传入的请求灵活地重定向到用户。

要使用redirects, 请在next.config.js 文件中添加此选项:

module.exports = {
async redirects() {
return [
// Basic redirect
{
source: "/about",
destination: "/",
permanent: true,
},
// Wildcard path matching
{
source: "/blog/:slug",
destination: "/news/:slug",
permanent: true,
},
];
},
};

参阅redirects API reference获取更多信息。

NextResponse.redirect in Middleware

Middleware 允许您在请求完成前运行代码。然后,基于传入的请求, 使用NextResponse.redirect重定向到不同的 URL。这在您想基于状态重定向用户时非常有用,如: (e.g. authentication, session management, 等) 或有 大量重定向.

例如, 如果用户身份认证失败,将用户重定向到/login页面:

import { NextResponse, NextRequest } from "next/server";
import { authenticate } from "auth-provider";

export function middleware(request: NextRequest) {
const isAuthenticated = authenticate(request);

// If the user is authenticated, continue as normal
if (isAuthenticated) {
return NextResponse.next();
}

// Redirect to login page if not authenticated
return NextResponse.redirect(new URL("/login", request.url));
}

export const config = {
matcher: "/dashboard/:path*",
};
import { NextResponse } from "next/server";
import { authenticate } from "auth-provider";

export function middleware(request) {
const isAuthenticated = authenticate(request);

// If the user is authenticated, continue as normal
if (isAuthenticated) {
return NextResponse.next();
}

// Redirect to login page if not authenticated
return NextResponse.redirect(new URL("/login", request.url));
}

export const config = {
matcher: "/dashboard/:path*",
};

您需要知道:

  • Middleware 在next.config.jsredirects之后、渲染之前运行。

参阅Middleware 文档获取更多信息。

大规模管理重定向(高级)

为了大规模管理重定向(1000+), 您可以考虑使用 Middleware 来创建自定义解决方案。这允许您在无需重新部署应用程序的条件下以编程方式处理重定向。

为此,您需要考虑:

  1. 创建和存储重定向映射。
  2. 优化数据查找性能。

1. 创建和存储重定向映射。

重定向映射是可以存储在数据库(通常是键值对存储)或 JSON 文件中的重定向列表。

考虑以下数据结构:

{
"/old": {
"destination": "/new",
"permanent": true
},
"/blog/post-old": {
"destination": "/blog/post-new",
"permanent": true
}
}

Middleware中, 您能够从 Vercel 数据库的Edge ConfigRedis中读取, 并基于传入的请求重定向用户:

import { NextResponse, NextRequest } from "next/server";
import { get } from "@vercel/edge-config";

type RedirectEntry = {
destination: string;
permanent: boolean;
};

export async function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname;
const redirectData = await get(pathname);

if (redirectData && typeof redirectData === "string") {
const redirectEntry: RedirectEntry = JSON.parse(redirectData);
const statusCode = redirectEntry.permanent ? 308 : 307;
return NextResponse.redirect(redirectEntry.destination, statusCode);
}

// No redirect found, continue without redirecting
return NextResponse.next();
}
import { NextResponse } from "next/server";
import { get } from "@vercel/edge-config";

export async function middleware(request) {
const pathname = request.nextUrl.pathname;
const redirectData = await get(pathname);

if (redirectData) {
const redirectEntry = JSON.parse(redirectData);
const statusCode = redirectEntry.permanent ? 308 : 307;
return NextResponse.redirect(redirectEntry.destination, statusCode);
}

// No redirect found, continue without redirecting
return NextResponse.next();
}

2. 优化数据查找性能

为每个传入请求读取大型数据集可能是缓慢的并成本很高。有两种方式可以优化数据查找性能:

  • 使用针对读取而进行优化了的数据集, 例如Vercel Edge ConfigRedis.
  • 使用数据查找策略,例如Bloom filter在读取较大的重定向文件或数据集前高效地检查是否存在重定向。

考虑到前面的实力, 您能够将生成的 bloom filter file 引入到 Middleware 中, 然后, 核对在 bloom filter 中是否存在传入的请求路径名。

如果存在, 将请求转发到Route Handler,它会核查实际文件并将用户重定向到合适的 URL。这会避免在 Middleware 中引入减慢传入请求的大量的重定向文件。

import { NextResponse, NextRequest } from "next/server";
import { ScalableBloomFilter } from "bloom-filters";
import GeneratedBloomFilter from "./redirects/bloom-filter.json";

type RedirectEntry = {
destination: string;
permanent: boolean;
};

// Initialize bloom filter from a generated JSON file
const bloomFilter = ScalableBloomFilter.fromJSON(GeneratedBloomFilter as any);

export async function middleware(request: NextRequest) {
// Get the path for the incoming request
const pathname = request.nextUrl.pathname;

// Check if the path is in the bloom filter
if (bloomFilter.has(pathname)) {
// Forward the pathname to the Route Handler
const api = new URL(
`/api/redirects?pathname=${encodeURIComponent(request.nextUrl.pathname)}`,
request.nextUrl.origin
);

try {
// Fetch redirect data from the Route Handler
const redirectData = await fetch(api);

if (redirectData.ok) {
const redirectEntry: RedirectEntry | undefined =
await redirectData.json();

if (redirectEntry) {
// Determine the status code
const statusCode = redirectEntry.permanent ? 308 : 307;

// Redirect to the destination
return NextResponse.redirect(redirectEntry.destination, statusCode);
}
}
} catch (error) {
console.error(error);
}
}

// No redirect found, continue the request without redirecting
return NextResponse.next();
}
import { NextResponse } from "next/server";
import { ScalableBloomFilter } from "bloom-filters";
import GeneratedBloomFilter from "./redirects/bloom-filter.json";

// Initialize bloom filter from a generated JSON file
const bloomFilter = ScalableBloomFilter.fromJSON(GeneratedBloomFilter);

export async function middleware(request) {
// Get the path for the incoming request
const pathname = request.nextUrl.pathname;

// Check if the path is in the bloom filter
if (bloomFilter.has(pathname)) {
// Forward the pathname to the Route Handler
const api = new URL(
`/api/redirects?pathname=${encodeURIComponent(request.nextUrl.pathname)}`,
request.nextUrl.origin
);

try {
// Fetch redirect data from the Route Handler
const redirectData = await fetch(api);

if (redirectData.ok) {
const redirectEntry = await redirectData.json();

if (redirectEntry) {
// Determine the status code
const statusCode = redirectEntry.permanent ? 308 : 307;

// Redirect to the destination
return NextResponse.redirect(redirectEntry.destination, statusCode);
}
}
} catch (error) {
console.error(error);
}
}

// No redirect found, continue the request without redirecting
return NextResponse.next();
}

然后, 在路由处理程序中:

import { NextRequest, NextResponse } from "next/server";
import redirects from "@/app/redirects/redirects.json";

type RedirectEntry = {
destination: string;
permanent: boolean;
};

export function GET(request: NextRequest) {
const pathname = request.nextUrl.searchParams.get("pathname");
if (!pathname) {
return new Response("Bad Request", { status: 400 });
}

// Get the redirect entry from the redirects.json file
const redirect = (redirects as Record<string, RedirectEntry>)[pathname];

// Account for bloom filter false positives
if (!redirect) {
return new Response("No redirect", { status: 400 });
}

// Return the redirect entry
return NextResponse.json(redirect);
}
import { NextResponse } from "next/server";
import redirects from "@/app/redirects/redirects.json";

export function GET(request) {
const pathname = request.nextUrl.searchParams.get("pathname");
if (!pathname) {
return new Response("Bad Request", { status: 400 });
}

// Get the redirect entry from the redirects.json file
const redirect = redirects[pathname];

// Account for bloom filter false positives
if (!redirect) {
return new Response("No redirect", { status: 400 });
}

// Return the redirect entry
return NextResponse.json(redirect);
}

您需要知道:

  • 你能够使用类似于bloom-filters的库来生成 bloom filter.
  • 您应该验证向路由处理程序发出的请求以预防恶意请求。