next.js: Add `app/` directory support for custom title in Error page

Describe the feature you’d like to request

I would like to have the possibility to control the page title, description etc. in the error and not-found page.

currently Its possible to get around not-found page by creating a conditional in metadata function for the not-found page but is still not nice.

Describe the solution you’d like

I would like not-found.js and error.js accept metadata and generateMetadata() similar to page.js and layout.js

// app/entity/[entityId]/not-found.js

export async function generateMetadata(props) {
  return {
      title: `entity ${props.params.entityId} not found`,
      description: `There is no entity with id: ${props.params.entityId}`
    }
}

export default function EntityNotFound() {
  return <div>Sorry this Entity was not found</div>;
}
// app/error.js

export const metadata = {
      title: "Error",
      description: "ups something went wrong"
    }

export default function Error() {
  return <div>Sorry this Entity was not found</div>;
}

Describe alternatives you’ve considered

for not-found, maybe allow to pass the metadata object as an argument for the function

export default async function AnimalPage(props) {
  const singleEntity = await getAnimalById(props.params.entityId);

  if (!singleEntity) {
    
    const metadata = {
      title: `entity ${props.params.entityId} not found`,
      description: `There is no entity with id: ${props.params.entityId}`
    }
    notFound(metadata);
  }

return <>
... rest of the component

}

About this issue

  • Original URL
  • State: open
  • Created a year ago
  • Reactions: 49
  • Comments: 30 (11 by maintainers)

Commits related to this issue

Most upvoted comments

Would love this feature as well. Running a i18n site with this folder structure -> [locale]/not-found.tsx

I want to be able to get the locale param so we can fetch correct translations for not-found page.

Workaround

I guess for a static title, this hack can be used with the App Router (doesn’t result in a hydration error, interestingly enough):

// app/not-found.tsx
export default function NotFound() {
  return (
    <div>
      {/*
        No support for metadata in not-found.tsx yet
        https://github.com/vercel/next.js/pull/47328#issuecomment-1488891093
      */}
      <title>Not Found | example.com</title>
    </div>
  );
}

Maybe this is what it will look like in the future once React Float more fully lands…

Or @gnoff @sebmarkbage should this already work now with the current state of React Float + Next.js? Was really surprised by lack of hydration error in both dev and production 🤔

Update Feb 2024: Maybe this is actually the new Document Metadata feature mentioned in the React Labs Feb 2024 “What We’ve Been Working On” blog post:

Document Metadata: we added built-in support for rendering <title>, <meta>, and metadata <link> tags anywhere in your component tree. These work the same way in all environments, including fully client-side code, SSR, and RSC. This provides built-in support for features pioneered by libraries like React Helmet.

A related issue I don’t see mentioned here is that without metadata in error pages, the viewport meta tag is missing and mobile layouts are broken.

@huozhi Any ETA on when params support will be added to the not-found.js page? I find it hard to believe that Next doesn’t support localized 404 pages out of the box, that seems like a pretty important feature.

I see, dynamic routes with params makes sense. We’ll think of support for that case

Did some research around metadata support for not-found and error convention. It’s possible to support in not-found when it’s server components as currently metadata is only available but for error it has to be client components then it’s hard to support it there as it’s a boundary. To support that it requires more changes in next.js to make them possible.

Meanwhile, to solve the issue of page titles on 404-pages, assuming you have:

  • /[id]
    • /page.tsx
    • /not-found.tsx

Then your not-found metadata currently cannot come from your not-found.tsx file, instead, you are to put it into your page.tsx file:

type Params = {
  id: string;
};

const metadata: Metadata = {
  title: 'My page',
  description: 'Viewing a single page',
};

export async function generateMetadata({
  params,
}: {
  params: Params;
}): Promise<Metadata> {
  const exists = await pageExists(Number(params.id));
  if (!exists) {
    return {
      title: 'Page not found',
      description: 'This page could not be found.',
    };
  }
  return metadata;
}

export default async function Page({ params }: { params: Params }) {
  const exists = await pageExists(Number(params.id));

  if (!exists) {
    notFound();
  }

  return (
    <p>
      Your page goes here.
    </p>
  );
}

My issue with this is that I want the not-found page to be exclusively responsible for deciding how a 404-situation would look and feel. Now I need to make two files responsible for it, and I even need to do pageExists in both the page component and the generateMetadata function.

But at least it works 😃

Should generateMetadata work in not-found file then? I’m testing in v13.4.19 and it’s nothing.

but might only support metadata as you could keep use notFound() use inside generateMetadata() which could cause more issues

Oh too bad, so no way to generate dynamic titles / descriptions based on the requested content 🙁

Maybe there would still be some ways to support generateMetadata() eg:

  1. If notFound() used within generateMetadata() in not-found.tsx / error.tsx, throw an error
  2. Also include a lint rule to warn users before it happens

Next.js 14.0.4 using App Router and not-found.tsx:

export const metadata = {
  title: 'Page not found',
  description: 'This page could not be found',
};

It works when I load the page, briefly, but the page.tsx in the same folder overwrites the title immediately.

Tested in developer-mode. Obviously, that should not happen.

Reading the docs: https://nextjs.org/docs/app/building-your-application/optimizing/metadata

…also doesn’t clear up the issue. Pages like not-found.tsx aren’t part of the hierarchy, I suppose.

Should generateMetadata work in not-found file then? I’m testing in v13.4.19 and it’s nothing.

It only doesn’t work during development, if you make a build, the metadata is displayed correctly. I also noticed that if you specify robots, it is duplicated on the page 2 times. Initially, next adds robots to the page with the value noindex.

Nope… at least for me, I’m testing 14.0 and my issue is still there. I have title and description defined statically in a parent layout file, and then in a child route an error file that simply tries to redefine them statically. When the error page loads, it shows for a fraction of second the correct metadata, and then it reverts back to the layout (parent) one. This happens in both dev and prod builds… I agree that Next has a ton of weird issues in apparently basic features, it makes me miss the good old client-server model…

@huozhi The not found page is refreshing every 3 seconds and we don’t have <link rel="icon" href="/favicon.ico" type="image/x-icon" sizes="any"> tag.

Currently, I need to manually add <meta name="viewport" content="width=device-width, initial-scale=1" ></meta> into the not found page in order to make it responsive

@bacvietswiss that issue is fixed in #50044

Currently, I need to manually add <meta name="viewport" content="width=device-width, initial-scale=1" ></meta> into the not found page in order to make it responsive

I’m also hoping this feature is added soon. We were able to do this in the pages directory using <Head>, so it’d be nice to have it with the app directory too 😄

Just like @karlhorky mentioned

<div>
      <title>Not Found</title>
  </div>

works. But I am using template

export const metadata: Metadata = {
  title: {
    template: '%s | My Website Name',
    default: 'My Website Name'
  }

So for someone like me, I had to write the below code

<div>
      <title>Not Found | My Website Name</title>
  </div>

The <title> in my not-found.tsx replaces my template’s fallback Title as well. I know its obvious, but I just wanted to let ya’ll know

@itsjavi One possible workaround for that is similar to https://github.com/vercel/next.js/issues/48763#issuecomment-1623745516

…you can use a catch all route and put a page.js under it, and then populate generateMetadata.

A possibly simpler interim solution to keep everything in not-found.js may be to just update the title in the client side DOM e.g.

useEffect(() => document.title = "Page Not Found")

…as I suppose for error pages other meta tags are probably less important and don’t really need SSR support.

However, I agree with this issue that error pages should have their own generateMetadata method, as it would be a cleaner and more consistent.

Regarding the custom metadata for the not-found.js page, maybe I’ve found a workaround!

Currently, we are facing a problem where we throw a notFound() error when no page is found in our dynamic route. At that time, we don’t see the meta title and description of the not-found.js page. Instead, we see the meta data of the current page. I believe this is logical and not a bug in Next.js. In the case of a dynamic route, the meta data of the not-found.js page should also be dynamic, as it might be useful in some scenarios.

So, in the case of a dynamic route, we can set the meta data for the not-found.js page dynamically from page.js. It’s important to note that the generateMetadata function should not throw a notFound error. Instead, the page component should throw a notFound error. For example, you can see the screenshot below. I hope this solution works.

My folder structure:

app/[locale]/[...slugs]/page.js
app/[locale]/[...slugs]/not-found.js
app/[locale]/[...slugs]/error.js

Screenshot 2023-07-09 221946

@sayhicoelho have you ever fixed the refreshing thing?

No. I give up Next for now. I had a lot of problems and a lot of weird bugs.

I have something working for me. I determine if I am in a 404 state in generateMetadata for a route like. [blah]/page.tsx and return something like { title: ‘Not Found’ } in [blah]/not-found.tsx I just have the error markup. The meta data from page.tsx is present when I get my 404 hit I get the metadata set in page.tsx + <meta name="robots" content="noindex"/> added by the notFound() call. (notFound() is called in page.tsx’s component tree, not in generateMetadata of [blah]/page.tsx

Sure, I was thinking of something kind of similar to the one in the issue description:

// app/animals/[animalId]/not-found.js

export async function generateMetadata(props) {
  return {
    title: `Animal id ${props.params.animalId} not found`,
    description: `There is no animal with id "${props.params.animalId}"`,
  };
}

export default function AnimalNotFound(props) {
  return <div>Sorry, an animal with id "{props.params.animalId}" was not found</div>;
}