重定向
在 Next.js 中有几种方式可以处理重定向。本页会介绍每个可选项、用例和如何管理大量重定向。
| API | Purpose | Where | Status Code |
|---|---|---|---|
redirect | 在突变或事件后重定向用户 | 服务器组件,服务器操作,路由处理程序 | 307 (暂时的) 或 303 (服务器操作) |
permanentRedirect | 在突变或事件后重定向用户 | 服务器组件,服务器操作,路由处理程序 | 308 (永久的) |
useRouter | 执行客户端导航 | 客户端组件中的事件处理程序 | N/A |
redirects in next.config.js | 根据路径重定向传入请求 | next.config.js file | 307 (暂时的) or 308 (永久的) |
NextResponse.redirect | 根据状态重定向传入请求 | Middleware | Any |
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可能会在渲染过程中而不是事件处理程序期间在客户端组件中调用。您可以使用useRouterhook 代替.redirect也接受绝对路径,并且重定向到外部链接。- 如果您想在渲染程序之前重定向, 使用
next.config.js或 Middleware.
参阅 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.js或 Middleware.
参阅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.js的redirects之后、渲染之前运行。
参阅Middleware 文档获取更多信息。
大规模管理重定向(高级)
为了大规模管理重定向(1000+), 您可以考虑使用 Middleware 来创建自定义解决方案。这允许您在无需重新部署应用程序的条件下以编程方式处理重定向。
为此,您需要考虑:
- 创建和存储重定向映射。
- 优化数据查找性能。
1. 创建和存储重定向映射。
重定向映射是可以存储在数据库(通常是键值对存储)或 JSON 文件中的重定向列表。
考虑以下数据结构:
{
"/old": {
"destination": "/new",
"permanent": true
},
"/blog/post-old": {
"destination": "/blog/post-new",
"permanent": true
}
}
在Middleware中, 您能够从 Vercel 数据库的Edge Config 或 Redis中读取, 并基于传入的请求重定向用户:
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 Config 或 Redis.
- 使用数据查找策略,例如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.- 您应该验证向路由处理程序发出的请求以预防恶意请求。