You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)和其他服务器错误,分别处理:

  1. 修改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();
    }
    
  2. 修复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,
        },
      };
    }
    
  3. 移除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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 08:03:19