Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add app/ directory support for custom title in Error page #45620

Open
Josehower opened this issue Feb 6, 2023 · 33 comments
Open

Add app/ directory support for custom title in Error page #45620

Josehower opened this issue Feb 6, 2023 · 33 comments
Assignees
Labels
linear: next Confirmed issue that is tracked by the Next.js team.

Comments

@Josehower
Copy link
Contributor

Josehower commented Feb 6, 2023

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

}
@Josehower Josehower changed the title Add app/dir support for custom title in Error page Add app/ directory support for custom title in Error page Feb 6, 2023
@Josehower Josehower changed the title Add app/ directory support for custom title in Error page Add app/ directory support for custom title in Error page Feb 6, 2023
@karlhorky
Copy link
Contributor

Interesting, seems like Shu was able to get a document.title on this example page

Screenshot 2023-03-29 at 17 54 29

This was part of this PR:

Not sure how this was achieved, I've asked over here:

@karlhorky
Copy link
Contributor

karlhorky commented Mar 29, 2023

I was actually having a different problem to this one just now - the metadata from the root layout is also not currently (as of next@13.2.5-canary.20) applied to not-found.tsx 404 pages (maybe also error.tsx?) 😯

StackBlitz: https://stackblitz.com/edit/vercel-next-js-znhsus?file=app%2Flayout.tsx&file=app%2Fnot-found.tsx&file=package-lock.json

2023-03-29_18-08

@karlhorky
Copy link
Contributor

karlhorky commented Mar 29, 2023

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.

@huozhi huozhi self-assigned this Apr 2, 2023
@huozhi huozhi added kind: story Metadata Related to Next.js' Metadata API. and removed template: story labels Apr 2, 2023
@huozhi
Copy link
Member

huozhi commented Apr 2, 2023

We're thinking to support it but might only support metadata as you could keep use notFound() use inside generateMetadata() which could cause more issues

@karlhorky
Copy link
Contributor

karlhorky commented Apr 2, 2023

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

@huozhi
Copy link
Member

huozhi commented Apr 2, 2023

@karlhorky error checking is possible to be added there to avoid using notFound in error/not-found page convention. Just curious, mind sharing the dynamic title/description case for your not-found page?

@karlhorky
Copy link
Contributor

karlhorky commented Apr 2, 2023

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>;
}

@huozhi
Copy link
Member

huozhi commented Apr 2, 2023

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

@jahirfiquitiva
Copy link

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 😄

@lindesvard
Copy link

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.

@tobobo
Copy link

tobobo commented Apr 29, 2023

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.

@bitfrost
Copy link

bitfrost commented May 3, 2023

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 + added by the notFound() call. (notFound() is called in page.tsx's component tree, not in generateMetadata of [blah]/page.tsx

@bacvietswiss
Copy link

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

@leonbe02
Copy link

@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.

@huozhi
Copy link
Member

huozhi commented May 20, 2023

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

@huozhi
Copy link
Member

huozhi commented May 20, 2023

@leonbe02 Can you file another issue about that as the original issue here is related to metadata support

@leonbe02
Copy link

@leonbe02 Can you file another issue about that as the original issue here is related to metadata support

I found an existing ticket that outlines the issue: #48763

@sayhicoelho
Copy link

@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.

@timneutkens timneutkens added linear: next Confirmed issue that is tracked by the Next.js team. and removed linear: next Confirmed issue that is tracked by the Next.js team. kind: story Metadata Related to Next.js' Metadata API. labels Jul 4, 2023
@padmaia padmaia added linear: next Confirmed issue that is tracked by the Next.js team. and removed linear: next Confirmed issue that is tracked by the Next.js team. labels Jul 6, 2023
@MuhammadMinhaj
Copy link

MuhammadMinhaj commented Jul 9, 2023

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

@itsjavi
Copy link

itsjavi commented Jul 9, 2023

@MuhammadMinhaj nice, but the workaround still doesn't solve de problem for the catch-all unmatched routes

@p00000001
Copy link

p00000001 commented Jul 9, 2023

@itsjavi One possible workaround for that is similar to #48763 (comment)

...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.

@huozhi
Copy link
Member

huozhi commented Jul 14, 2023

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.

kodiakhq bot pushed a commit that referenced this issue Jul 14, 2023
### What?

Support metadata exports for `not-found.js` conventions

### Why?

We want to define metadata such as title or description basic properties for error pages, including 404 and 500 which referrs to `error.js` and `not-found.js` convention. See more requests in #45620 

Did some research around metadata support for not-found and error convention. It's possible to support in `not-found.js` when it's server components as currently metadata is only available but for `error.js` it has to be client components for now so it's hard to support it for now as it's a boundary.

### How?

We determine the convention if we're going to render is page or `not-found` boundary then we traverse the loader tree based on the convention type. One special case is for redirection the temporary metadata is not generated yet, we leave it as default now.

Fixes #52636
@levipadre
Copy link

levipadre commented Sep 1, 2023

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

@DennieMello
Copy link

DennieMello commented Sep 14, 2023

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.

@pfurini
Copy link

pfurini commented Oct 31, 2023

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...

@Super-Kenil
Copy link

Super-Kenil commented Nov 27, 2023

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

@marcelhageman
Copy link

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.

@marcelhageman
Copy link

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 :)

@trevorblades
Copy link

trevorblades commented May 16, 2024

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

I'm running into this issue now too, and it feels very unintuitive. I'd expect that when I call notFound() in a page, it's a signal to Next that this page should appear as nonexistent, and the entirety of my not-found.tsx route should render, metadata included.

Having the underlying page title show up breaks that experience. Suppose I have a dynamic page that fetches some resource and checks to see if the current user should be able to view it. If they shouldn't, then I want to tell them that the resource doesn't exist by rendering our 404 page.

If the name of the resource shows up in the page title, it feels like a leaking of information that the current user shouldn't have access to. Of course, I can do the additional check in the page's generateMetadata function as @marcelhageman suggests above, but that's definitely a hacky workaround to this unexpected behaviour.

Edit: I figured out that I can get the not-found.tsx UI and metadata to render correctly by calling notFound() from within my generateMetadata function.

export const generateMetadata = async ({ params }: { params: id }): Promise<Metadata> => {
  const thing = await getThing();
  
  if (!thing) {
    notFound();
  }
  
  const { user } = await getCurrentUser();
  
  if (!user || !hasAccess(user, thing)) {
    notFound();
  }
  
  return {
    title: `Custom title for ${thing.name}`
  }
}

However this also feels like a hack and unexpected behaviour. Just wanted to mention this here in case others need a workaround while this issue is being addressed.

@shakibhasan09

This comment has been minimized.

@mobeigi
Copy link

mobeigi commented Sep 19, 2024

This is my usecase and some notes on it:

I have:
/app/not-found.tsx
/app/blog/[...slug]/page.tsx

When visiting:
http://localhost:3000/blog/non-existant-page

The /app/blog/[...slug]/page.tsx route fires as expected.

/app/blog/[...slug]/page.tsx

export const generateMetadata = async ({ params }: { params: { slug: string[] } }): Promise<Metadata> => {
  const post = await getPostFromParams({ params });
  if (!post) {
    console.warn('Failed to find post during generateMetadata.');
    return {
      title: 'fallback';
    };
  }
 return {
   title: 'Blog!';
 } 
}

const BlogPostHandler = async ({ params }: { params: { slug: string[] } }) => {
  const post = await getPostFromParams({ params });
  if (!post) {
    notFound();
    return null;
  }
  //other logic
}

not-found.tsx:

import NotFoundPage from '@/containers/NotFoundPage';
import { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'Not Found',
};

const NotFound = () => <NotFoundPage />;

export default NotFound;

The behaviour I observe is:

  • title is originally Not Found which is the root layouts title (this is also what is present in the initial HTML)
  • the title is then fallback which is what the page.tsx resolves to.

Now if both your page.tsx and not-found.tsx page have the same meta data (i.e. static metadata object you return in both for the not found case) then this works pretty well. If not, there will be a change from either the layout meta data or not-found.tsx metadata to whatever the page.tsx metadata is.

PS: You should probably use notFound() from inside the generateMetadata.

It would be nice if we could export metadata as null or return null for generateMetadata to tell next to not use this metadata and fallback to the previously available meta data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
linear: next Confirmed issue that is tracked by the Next.js team.
Projects
None yet
Development

No branches or pull requests