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

Unable to have global layout - custom "not-found" 404 page and global Metadata #6695

Closed
todor0v opened this issue Jun 9, 2024 · 7 comments
Closed
Assignees
Labels

Comments

@todor0v
Copy link

todor0v commented Jun 9, 2024

Link to reproduction

No response

Payload Version

3.0.0-beta.43

Node Version

20.13.1

Next.js Version

15.0.0-rc.0

Describe the Bug

@jacobsfletch This is related to #6511, however I believe that it is about more than just 404 pages, so I am creating a new issue. Let me know if you do not approve and I will comment there instead.

I have three main problems that I cannot solve with the current layout.tsx logic:

  • I want to have global Metadata
  • I want to have one global layout.tsx
  • I want to have a custom global 404 page

First I read the blog post on the beta install and as I understood, if I have layout.tsx file inside of app/(app), that should work globally (I should be able to have the same metadata for all pages for example (and then have individual ones if I want to). The more I read through the Demo code, however, the more I don't see how would this be possible. The layout file in app/(app) is not a global layout file. Which means that I cannot use it for meta, or 404. This has the additional problem that every time I create a new page, Next.js generates a layout.tsx automatically for that path. I end up with multiple layout files that are essentially the same thing, because the main one cannot be used.

At the same time I cannot just create a layout.tsx directly inside of app. This clashes with the Payload /admin page. This is understandable, but as of now I cannot find a good way to solve my problems. What I was able to do so far is this:

src/app/layout.tsx:

import type { Metadata } from "next";
import "@/css/index.css";
import { getPathname } from "@nimpl/getters/get-pathname";

export const metadata: Metadata = {
	title: "My Website",
};

export default function RootLayout({
	children,
}: Readonly<{
	children: React.ReactNode;
}>) {
	const pathname = getPathname();

	if (pathname?.includes("/admin")) {
		return children;
	} else {
		return (
			<html lang="en">
				<body>
					<div>{children}</div>
				</body>
			</html>
		);
	}
}

Ideally Next.js should provide a way for me to check the pathname on the server, however as per this issue, it currently does not. I had to use third party solution to reliably get the pathname and based on that either return the children directly (thus not affecting the admin panel) or the page (thus being able to have a custom layout.

Would you please advise me if there is or there will be a better way to do this in the future?

Thank you!

cc: @jmikrut since you assigned the original issue

Reproduction Steps

  • use the Demo repo of Payload 3 or npx create-payload-app@beta
  • use the layout.tsx inside app/(app) as advised
  • you cannot have global layout this way or a global 404 page

Adapters and Plugins

db-mongodb, babel-plugin-react-compiler

@todor0v todor0v added status: needs-triage Possible bug which hasn't been reproduced yet v3 labels Jun 9, 2024
@github-actions github-actions bot removed the status: needs-triage Possible bug which hasn't been reproduced yet label Jun 10, 2024
@bryanjhickey
Copy link

@todor0v Why can't you define global metadata in app/(app)/layout.tsx? I've been doing this for years.

@todor0v
Copy link
Author

todor0v commented Jun 13, 2024

@bryanjhickey I could if I have the other pages in app/(app) as well. I believe that I wrongly interpreted the Payload 3.0 Demo and though that they should be placed in app (outside of (app). Once you do that, Next.js will start creating "Generated by Next.js" layout.tsx files and the meta that is in app/(app)/layout.tsx will be ignored. However, what they did is just use that place for a route.ts file that is not a page. So you are correct. If I have for example: app/(app)/about, that could use the layout.tsx with its metadata. This still doesn't fix the global 404 page problem though, because not-found.tsx would have to live outside of app/(app)/ and will lack a layout file.

@todor0v
Copy link
Author

todor0v commented Jun 13, 2024

I also found another weird behavior that I cannot debug so far.
With my current setup (a global layout.tsx file in src/app that checks for pathname?.includes("/admin")), if you go to a 404 page (for example https://domain.com/wsdfawdf), the latest content from Payload CMS displays as it should. You can update it and see the changes. However if you go to https://domain.com/404 specifically, the content is whatever it was after the last build process. I tried different variations of revalidatePath with an afterChange hook with no luck so far.

@todor0v
Copy link
Author

todor0v commented Jun 14, 2024

I believe that I found a better solution

So to summarize, the solutions are as follows:

  • I want to have global Metadata - use export const metadata inside of app/(app)/layout.tsx
  • I want to have one global layout.tsx - use app/(app)/layout.tsx
  • I want to have a custom global 404 page - create an app/laout.tsx with the following content:
export default function RootLayout({
    children,
}: Readonly<{
    children: React.ReactNode;
}>) {
    return children;
}

Then create an app/not-found.tsx file that should contain a whole page (<html>, <body> etc.).

This will solve all of the problems without resolving to checking pathname. Because of the global layout.tsx just returning the children, there is no issue with the Payload files, /admin works normally and not-found.tsx doesn't throw any errors because it has all its necessary html elements.

@jacobsfletch If you believe this to be an acceptable approach, this issue can be closed. Also maybe we could mention something in the docs for Payload 3 regarding a custom global 404 page.

Ok scratch that. If you do this solution for a 404 page, everything that is an inner path of an existing page, but results in 404 (such as /blog/wasdfwas) will result in an error: In HTML, <html> cannot be a child of <div>. So I still have to use my original solution and look for the pathname because some paths will need body and html and some won't. However, this time it's harder since we don't just look for admin.

@todor0v todor0v changed the title Unable to have global layout Unable to have global layout - custom "not-found" 404 page and global Metadata Jun 14, 2024
@jacobsfletch
Copy link
Member

@todor0v first of all, thank you for the details here. Great to see and very helpful. I went ahead and took a quick pass at #6511 just now and found a temporary workaround. Check out my comment here for details.

Let me address a few of your points directly:

I want to have global Metadata
I want to have one global layout.tsx
I want to have a custom global 404 page

When you say "global" I assume you mean throughout every page within your own application, i.e. not including the admin panel. So for example, you can control metadata for your "home" page and your "about" page from the same location.

This should already work as-is.

In your application you should have a (app) route group directly alongside (payload). In there, you can add a layout.tsx file which manages all metadata for that route group, just as @bryanjhickey suggested. Same with the error page—a workaround has been posted to this Next.js discussion.

Here's the setup:

app
├── (app)
│   ├── [...not-found]
│   │   └── page.tsx
│   ├── layout.tsx
│   ├── not-found.tsx
│   └── page.tsx
── (payload)
   ├── admin
   └── layout.tsx

In (app)/[...not-found]/page.tsx:

import { notFound } from 'next/navigation';

export default function NotFoundDummy() {
 notFound();
}

In (app)/layout.tsx:

export const metadata = {
  title: 'This is global metadata for your app',
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

@jacobsfletch jacobsfletch closed this as not planned Won't fix, can't repro, duplicate, stale Jul 29, 2024
@todor0v
Copy link
Author

todor0v commented Jul 31, 2024

@jacobsfletch Thank you so much for the help! This solution works for me and I can have a global 404 page without the workaround with returning different code structure for the panel and the global layout.

I only have one problem with this approach that I discovered. If you go to /404 specifically, you will see the default 404 page and not the custom one. Any other URL that triggers a 404 is fine, but /404 for some reason is not. Can I fix that? Mind you this only happens on production (npm run start).

Copy link

github-actions bot commented Sep 6, 2024

This issue has been automatically locked.
Please open a new issue if this issue persists with any additional detail.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 6, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants