How to return 404 status code in NextJS?

I’m using Builder.io with Next.js. In [...page].jsx I have the following code

  const router = useRouter();
  const isPreviewing = useIsPreviewing();

  // If the page content is not available
  // and not in preview mode, show a 404 error page
  if (!page && !isPreviewing) {
    return <Custom404 />;
  }

  // If the page content is available, render
  // the BuilderComponent with the page content
  return (
    <>
      <Head>
        <title>{page?.data?.title}</title>
      </Head>
      {/* Render the Builder page */}
      <Layout>
        <BuilderComponent model="page" content={page || undefined} />
      </Layout>
    </>
  );
}

This way, it the page is not found, it returns 200 status code, but I’d like it to be 404

I can return 404 code from getStaticProps as below

  // Fetch the builder content for the given page
  const page = await builder
    .get("page", {
      userAttributes: {
        urlPath: "/" + (params?.page?.join("/") || ""),
      },
    })
    .toPromise();

  if (!page) {
    return {
      notFound: true
    };
  }

  // Return the page content as props
  return {
    props: {
      page: page || null,
    },
    // Revalidate the content every 5 seconds
    revalidate: 5,
  };
};```

But this way I will loose preview for the new unpublished pages. What's the correct way to return 404 for not found pages, but keep the preview?

Hello @vyatsun,

In Next.js, you can return a 404 status code by using the NotFound component provided by Next.js. You can use this component in your pages or API routes to indicate that the requested resource could not be found.

Here is an example of how to return a 404 status code in a Next.js page using the NotFound component:

Create a catch-all route file in your app directory. This will handle dynamic routes and render 404 for non-existent pages.

// app/[...page]/page.tsx

import { builder } from "@builder.io/sdk";
import Head from "next/head";
import { RenderBuilderContent } from "@/components/builder";
import NotFound from "next/error"; // Import the NotFound component

// Replace with your Public API Key
builder.init("YOUR_API_KEY");

interface PageProps {
  params: {
    page: string[];
  };
}

export default async function Page(props: PageProps) {
  const page = await builder
    .get("page", {
      userAttributes: {
        urlPath: "/" + (props?.params?.page?.join("/") || ""),
      },
      prerender: false,
    })
    .toPromise();

  if (!page) {
    // If page is not found, render the NotFound component with 404 status
    return <NotFound statusCode={404} />;
  }

  return (
    <>
      <Head>
        <title>{page?.data.title}</title>
      </Head>
      <RenderBuilderContent model="page" content={page} />
    </>
  );
}

Hello @manish-sharma

It still returns 200 status, and a generic 404 page, while what I want is to return a custom 404 page with 404 status

Hello @vyatsun,

To ensure that your Next.js application returns an actual 404 status code along with a custom 404 page, you will need to use dynamic routing along with custom error handling. Below is an example that demonstrates how to achieve this:

  1. Create a custom 404 page: Next.js allows you to create a custom 404 page by creating a 404.js file in the pages directory.
// pages/404.js
import Link from 'next/link';
import { Head } from 'next/document';

export default function Custom404() {
  return (
    <>
      <Head>
        <title>Page Not Found</title>
      </Head>
      <div style={{ textAlign: 'center', padding: '50px' }}>
        <h1>404 - Page Not Found</h1>
        <p>Sorry, the page you are looking for does not exist.</p>
        <Link href="/">
          <a>Go back to Home</a>
        </Link>
      </div>
    </>
  );
}
  1. Create a catch-all route to handle dynamic pages: This file detects if a page exists and shows your custom 404 page if it doesn’t.
// app/[...page]/page.tsx
import { builder } from '@builder.io/sdk';
import Head from 'next/head';
import { RenderBuilderContent } from '@/components/builder';
import Custom404 from '@/pages/404'; // Import your custom 404 page

// Replace with your Public API Key
builder.init('YOUR_API_KEY');

interface PageProps {
  params: {
    page: string[];
  };
}

export default async function Page(props: PageProps) {
  const page = await builder
    .get('page', {
      userAttributes: {
        urlPath: '/' + (props?.params?.page?.join('/') || ''),
      },
      prerender: false,
    })
    .toPromise();

  if (!page) {
    return <Custom404 />;
  }

  return (
    <>
      <Head>
        <title>{page?.data.title}</title>
      </Head>
      <RenderBuilderContent model="page" content={page} />
    </>
  );
}

This custom file is correctly returning the status code set by Next.js by itself, which will be 404 due to the custom page.

@ manish-sharma

This code doesn’t work. Aren’t you supposed to call async functions to query for the page inside the getStaticProps function? Or at least within a hook? Custom404 is just a React component. I don’t get why it should return 404 status code. Compare your code with my code that I pasted in the initial question. What’s the difference?

Hello @vyatsun,

It depends on your deployment server response for the 404 page. You can possibly use getInitialProps on the custom page to return a 404 status like shown below

Custom404.getInitialProps = ({ res }) => {
  if (res) {
    res.statusCode = 404; // Set HTTP status code to 404
  }
  return {};
};

export default Custom404;

I still have the same problem.
Any updates on this topic?
I’ve noticed that even the Builder.io 404 page doesn’t return a 404 status code.