Next.js(App Router)如何返回404状态码并加载404页面?
问题
使用Next.js的App Router与Server Components时,当前服务器响应成功(数据库找到记录)页面正常加载;但记录不存在或出现服务器错误时,会触发error.tsx错误边界页面。开发阶段这没问题,但部署后页面被搜索引擎索引,后续删除页面时,Google需要获取404状态码来移除索引,想配置成错误时返回正确404状态码并加载not-found.tsx页面。
页面组件代码
import Nodata from "@/components/Nodata"; import { IPost } from "@/misc/types"; import Post from "@/wrappers/Post"; import { Metadata, ResolvingMetadata } from "next"; import React from "react"; const HOST = process.env.NEXT_PUBLIC_HOST; type Props = { params: { slug: string }; searchParams: { [key: string]: string | string[] | undefined }; }; export async function generateMetadata( { params, searchParams }: Props, parent: ResolvingMetadata ): Promise<Metadata> { // read route params const slug = params.slug; // fetch data const data: IPost[] = await fetch(`${HOST}/api/news/${String(slug)}`, { cache: "no-store", }) .then((res) => res.json()) .catch((err) => { throw new Error("Failed to fetch data"); }); const content = data[0]; return { metadataBase: new URL(String(HOST)), title: content.title, description: content.description, openGraph: { url: `${HOST}/news/${slug}`, title: content.title, description: content.description, }, }; } async function getData(slug: string) { const res = await fetch(`${HOST}/api/news/${slug}`, { cache: "no-store", }); // The return value is *not* serialized // You can return Date, Map, Set, etc. if (!res.ok) { // This will activate the closest `error.js` Error Boundary throw new Error("Failed to fetch data"); } return res.json(); } export default async function Page({ params, searchParams }: Props) { const { slug } = params; const data = await getData(slug); if (data) { return <Post data={data[0]} slug={slug} />; } else return <Nodata />; }
error.tsx(错误边界组件)
"use client"; // Error components must be Client Components import ErrWrapper from "@/wrappers/ErrWrapper"; import { useEffect } from "react"; export default function Error({ error, reset, }: { error: Error; reset: () => void; }) { useEffect(() => { // Log the error to an error reporting service console.error(error); }, [error]); return <ErrWrapper title="Whoops! Something went wrong..." img="oops.png" />; }
not-found.tsx(404页面)
import ErrWrapper from "@/wrappers/ErrWrapper"; export default function NotFound() { return <ErrWrapper title="404 - Document not found" img="404.gif" />; }
解决方案
要实现返回404状态码并加载not-found.tsx,需要修改数据获取逻辑,区分资源不存在(404)和其他服务器错误,分别处理:
修改
getData函数,处理404并调用notFound()
从next/navigation导入notFound函数,当接口返回404时调用它,这会触发not-found.tsx并返回404状态码;其他错误则继续抛出,触发error.tsx:import { notFound } from "next/navigation"; // 新增导入 async function getData(slug: string) { const res = await fetch(`${HOST}/api/news/${slug}`, { cache: "no-store", }); if (res.status === 404) { // 资源不存在,触发404页面 notFound(); } if (!res.ok) { // 其他服务器错误,触发error.tsx throw new Error("Failed to fetch data"); } return res.json(); }修复
generateMetadata中的错误处理
当前generateMetadata在接口失败或数据为空时会直接抛出错误,导致触发error.tsx。需要同样处理404情况,同时判断数据是否存在:import { notFound } from "next/navigation"; // 新增导入 export async function generateMetadata( { params, searchParams }: Props, parent: ResolvingMetadata ): Promise<Metadata> { const slug = params.slug; const res = await fetch(`${HOST}/api/news/${String(slug)}`, { cache: "no-store", }); if (res.status === 404) { notFound(); } if (!res.ok) { throw new Error("Failed to fetch data"); } const data: IPost[] = await res.json(); const content = data[0]; if (!content) { // 数据为空,也视为资源不存在 notFound(); } return { metadataBase: new URL(String(HOST)), title: content.title, description: content.description, openGraph: { url: `${HOST}/news/${slug}`, title: content.title, description: content.description, }, }; }移除Page组件中的冗余判断
经过上面的处理,getData已经在数据不存在时触发了notFound,所以Page组件中不需要再判断if (data),直接渲染即可:export default async function Page({ params, searchParams }: Props) { const { slug } = params; const data = await getData(slug); return <Post data={data[0]} slug={slug} />; }
修改后效果:
- 当接口返回404或数据为空时,自动加载
not-found.tsx并返回404状态码,满足搜索引擎索引移除需求 - 其他服务器错误(如5xx)仍会触发
error.tsx,保留错误边界的处理逻辑
内容的提问来源于stack exchange,提问作者user11553898
相关产品推荐
相关产品推荐

