vercel / next.js

The React Framework
https://nextjs.org
MIT License
127.27k stars 27.02k forks source link

Creating a custom 404 page with Next 13 #45834

Closed sebpowell closed 1 year ago

sebpowell commented 1 year ago

What is the improvement or update you wish to see?

I've just finished upgrading to Next 13 – feels like a big improvement, well done for all your hard work.

Just one problem I seem to be having – creating a custom 404 page. According to the upgrade guide, the new app directory doesn't support this yet. I assumed that it might work by using the pages directory instead, but it doesn't seem to get picked up by that either. Is this expected? Or am I perhaps missing something?

Just to be clear, this is what I've tried:

/app
  404.tsx

and

/pages
  404.tsx

Neither of these seem to work.

Is there any context that might help us understand?

See above.

Does the docs page already exist? Please link to it.

https://beta.nextjs.org/docs/upgrade-guide#migrating-from-pages-to-app

daredevil3435 commented 1 year ago

I want to try if I can contribute to this issue. I'm complete newbie here. Can you guide me ?

Gawdfrey commented 1 year ago

Hey.

FYI, you should always link to a reproduction of your issue. It is almost always really difficult to help without.

Back to the question, 404 pages are only handled in the app directory when explicitly calling the notFound() function. Next will then show the closest NotFound page in the tree. To show 404 pages implicitly you need to locate them in the pages folder.

I have it working this way in this reproduction. Take and look and see if you are missing something from this setup :)

sebpowell commented 1 year ago

@Gawdfrey Thanks for the quick response! And apologies yes, I should have included this.

I checked your link, and mine is setup the same way, and for whatever reason just started working this morning 🤷. Maybe it was serving up a cached version or something.

Going to close this now, thank you for your help!

LawEyez commented 1 year ago

Just a quick question, is there a way to add the layout in app to the 404 page in pages?

Gawdfrey commented 1 year ago

Just a quick question, is there a way to add the layout in app to the 404 page in pages?

Not as I am aware of. The way I have done it previously is using _app if I wanted a common layout for both the 500 and 404 pages.

We have to wait until the implicit not found functionality arrives before we share the same layout unfortunately.

JamDev0 commented 1 year ago

Hey, mine it's kind of working, but when the 404 page is loaded it triggers the following erro:

image

I'm using the next middleware, could it be it?

(if i did something wrong in this comment i'm sory, i'm new in here)

Gawdfrey commented 1 year ago

Hey, mine it's kind of working, but when the 404 page is loaded it triggers the following erro:

image

I'm using the next middleware, could it be it?

(if i did something wrong in this comment i'm sory, i'm new in here)

Usually good to include a replication of your code. Hard to help without. As you say it might be middleware if it does any routing, but as said, hard to say without looking at any code.

JamDev0 commented 1 year ago

Alright, sory.

My middleware is like this:

import { validSubdomains } from '@utils/validSubdomainsList';
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';

export function middleware(request: NextRequest) {
  const reqHost = request.headers.get('host');

  const reqURL = new URL(request.url);

  const currentSubdomain = reqHost?.slice(0, reqHost.indexOf('.'));

  const isValidSubdomain = validSubdomains.find(
    (subdomain) => subdomain === currentSubdomain
  );

  const token = request.cookies.get('aplication.token')?.value;

  console.log("token", {token})

  // Verify with the api if the token is valid  

  const isIndexPage = reqURL.pathname === '/';

  if (isIndexPage) {
    if (!token) {
      return NextResponse.redirect(new URL(`/login`, request.url));
    }

    return NextResponse.redirect(new URL(`/dashboard`, request.url));
  }

  if (isValidSubdomain) {
    const isNotAuthenticatedRoute = [
      '/login',
      '/register',
      '/forgot',
    ].includes(reqURL.pathname);

    if (isNotAuthenticatedRoute && token) {
      return NextResponse.redirect(new URL(reqURL.pathname, request.url));
    }

    if (!isNotAuthenticatedRoute && !token) {
      return NextResponse.redirect(new URL(`/login`, request.url));
    }

    return NextResponse.rewrite(new URL(`${reqURL.pathname}`, request.url));
  } else {
    return NextResponse.rewrite(new URL("/subdomain-not-valid", request.url));
  }

}

export const config = {
  matcher: ['/((?!api|_next/static|favicon.ico).*)'],
};
Gawdfrey commented 1 year ago

Alright, sory.

My middleware is like this:


import { validSubdomains } from '@utils/validSubdomainsList';

import type { NextRequest } from 'next/server';

import { NextResponse } from 'next/server';

export function middleware(request: NextRequest) {

  const reqHost = request.headers.get('host');

  const reqURL = new URL(request.url);

  const currentSubdomain = reqHost?.slice(0, reqHost.indexOf('.'));

  const isValidSubdomain = validSubdomains.find(

    (subdomain) => subdomain === currentSubdomain

  );

  const token = request.cookies.get('aplication.token')?.value;

  console.log("token", {token})

  // Verify with the api if the token is valid  

  const isIndexPage = reqURL.pathname === '/';

  if (isIndexPage) {

    if (!token) {

      return NextResponse.redirect(new URL(`/login`, request.url));

    }

    return NextResponse.redirect(new URL(`/dashboard`, request.url));

  }

  if (isValidSubdomain) {

    const isNotAuthenticatedRoute = [

      '/login',

      '/register',

      '/forgot',

    ].includes(reqURL.pathname);

    if (isNotAuthenticatedRoute && token) {

      return NextResponse.redirect(new URL(reqURL.pathname, request.url));

    }

    if (!isNotAuthenticatedRoute && !token) {

      return NextResponse.redirect(new URL(`/login`, request.url));

    }

    return NextResponse.rewrite(new URL(`${reqURL.pathname}`, request.url));

  } else {

    return NextResponse.rewrite(new URL("/subdomain-not-valid", request.url));

  }

}

export const config = {

  matcher: ['/((?!api|_next/static|favicon.ico).*)'],

};

Cant see any apparent issues on a quick look. But the error says you are trying to route to "/dashboar" but in the middleware only "/dashboard" is mentioned. Might be worth checking out if that url is being botched somehow

JamDev0 commented 1 year ago

So, the lack of mention of '/dashboar' doesn't seem to have anything to do with the error. To test it, I removed the part that checks whether the user needs to be logged in for the route, and placed it somewhere else. However, it still throws the same error.

Currently, the middleware looks like this:

import { validSubdomains } from '@utils/validSubdomainsList';
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';

export function middleware(request: NextRequest) {
  const reqHost = request.headers.get('host');

  const reqURL = new URL(request.url);

  const currentSubdomain = reqHost?.slice(0, reqHost.indexOf('.'));

  const isValidSubdomain = validSubdomains.find(
    (subdomain) => subdomain === currentSubdomain
  );

  const token = request.cookies.get('medical-registration.token')?.value;

  console.log("token", {token})

  // Verify with the api if the token is valid  

  const isIndexPage = reqURL.pathname === '/';

  if (isIndexPage) {
    if (!token) {
      return NextResponse.redirect(new URL(`/login`, request.url));
    }

    return NextResponse.redirect(new URL(`/dashboard`, request.url));
  }

  if (!isValidSubdomain) {
    return NextResponse.rewrite(new URL("/subdomain-not-valid", request.url));
  }
}

export const config = {
  matcher: ['/((?!api|_next/static|favicon.ico).*)'],
};
iosifache commented 1 year ago

Hi @LawEyez,

An approach to put the 404 page in the app directory is to create an app/[others]/page.tsx file with the following content:

import { redirect } from "next/navigation";

export default async function NotFound() {
    redirect("/404");
}

In this way, any non-existent route will be served by the generic [others] route, which will redirect to /404. The latter will render the content of app/404/page.tsx, whose content you can freely change.

chaepling commented 1 year ago

@iosifache Hi! I think your solution is the most fastest way to custom 404 pages.

But I have a question, won't doing that affect SEO? From my opinion that solution seems to not have a status code of 404, but 301.

Does it work like next 12 version's 404 page? Asking out of curiosity. Thanks!

ssoima commented 1 year ago

Hi Guys, for me it worked to just create a 404.tsx page under /src/pages and import the style from '@/app/globals.css'

JamDev0 commented 1 year ago

@ssoima when you do it in that way a error isen't triggered? Something about hard navigations

elliotgaramendi commented 1 year ago

Just create /app/not-found.jsx

https://nextjs.org/docs/app/api-reference/functions/not-found https://nextjs.org/docs/app/api-reference/file-conventions/not-found

ssoima commented 1 year ago

@JamDev0, no error is triggered. Try it with a blank project.

ElliotHYLee commented 1 year ago

The current issue with the not-found is that it seems somehow throws an error at least on my side and "Link" tag doesn't work anymore. From the doc, the not-found component doesn't accept any props like reset() or so. Therefore, if I use not-found, the user need to force refresh the page after clikcing "Link href="/">Home</Llink"

So far, dyanmic catch all route seems the best option. And src/pages/404.tsx simply didn't work for me. Please enlightne me how you are all working around or any.

dstarosta commented 1 year ago

I have the same issue with the not-found.tsx page breaking Link elements.

joshuajeschek commented 1 year ago

Just create /app/not-found.jsx

https://beta.nextjs.org/docs/api-reference/file-conventions/not-found https://beta.nextjs.org/docs/api-reference/notfound

thanks for the links! another thing that caused me to trip, was that this is not triggered when using dynamicParams = false. Or am I missing something? Anyways, I am using dynamicParams again, and then use an explicit notFound() in my Page function :)

clevermiraz commented 1 year ago

I am facing a Similar Problem When Adding 404.tsx or 404.jsx to the app directory. Even on My Actual Route Not Working Properly.

it should be not-found.tsx

ElliotHYLee commented 1 year ago

seems not-found.ts working with the latest version for "Link"

UmerGillani36 commented 1 year ago

Thanks its working with not-found.jsx or not-found.tsx

offwork commented 1 year ago

app/not-found runs in the root directory, which means you can't go back to app/page.tsx. so when you wrote a path that is not in your app directory, you will be redirected to not-found, but you cannot return to the index/home page. Is it possible to make this change in RootLayout?

rtatarinov commented 1 year ago

We still need to get a custom layout page for 404

cameronthrntn commented 1 year ago

+1 on custom layouts for 404 pages, in my opinion we should be able to create a not-found folder to coincide with the not-found file where we can define custom layouts and components.

I'm currently building an app which would require a custom layout for the 404 page and without some major remodels across the codebase this isn't looking worth the work - unless of course I'm missing something, if anybody has been able to solve this problem (a different layout for a 404 than other pages - including root) I'd appreciate any advice.

In the meantime, I'll just have to watch and hope this get's tuned

github-actions[bot] commented 1 year ago

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.