patterntypescriptnextjsModerate
Custom 404 and 500 pages in App Router vs Pages Router
Viewed 0 times
Next.js 13+ App Router
custom 404custom 500not-found.tsxglobal-error.tsxerror pages_error.tsx
Problem
Pages Router custom error pages (pages/404.tsx, pages/500.tsx) don't work in App Router. Developers add them and wonder why the default Next.js error page still shows.
Solution
App Router uses different file conventions for error pages:
// App Router 404: app/not-found.tsx
export default function NotFound() {
return (
<html>
<body>
<h1>404 - Page Not Found</h1>
<a href='/'>Return Home</a>
</body>
</html>
);
}
// App Router global error: app/global-error.tsx
// MUST include <html> and <body> — it replaces the root layout
'use client';
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<html>
<body>
<h2>Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</body>
</html>
);
}
// Pages Router (still valid if using /pages):
// pages/404.tsx — custom 404
// pages/500.tsx — custom 500
// pages/_error.tsx — handles all errors
// App Router 404: app/not-found.tsx
export default function NotFound() {
return (
<html>
<body>
<h1>404 - Page Not Found</h1>
<a href='/'>Return Home</a>
</body>
</html>
);
}
// App Router global error: app/global-error.tsx
// MUST include <html> and <body> — it replaces the root layout
'use client';
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<html>
<body>
<h2>Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</body>
</html>
);
}
// Pages Router (still valid if using /pages):
// pages/404.tsx — custom 404
// pages/500.tsx — custom 500
// pages/_error.tsx — handles all errors
Why
App Router and Pages Router have completely separate conventions for error pages. App Router uses not-found.tsx and global-error.tsx with React's error boundary system. These must be in the app/ directory, not pages/.
Gotchas
- global-error.tsx must be a Client Component and must render <html> and <body> since it replaces the root layout
- not-found.tsx at the app root is used for all unmatched routes — no need for pages/404.tsx in App Router
- Segment-level error.tsx does NOT catch root layout errors — only global-error.tsx does
- In Pages Router, _error.tsx receives statusCode prop; in App Router, error.tsx receives error and reset
Code Snippets
Global error page with html/body (App Router)
// app/global-error.tsx
'use client';
export default function GlobalError({ reset }: { reset: () => void }) {
return (
<html><body>
<h1>Critical Error</h1>
<button onClick={reset}>Reload</button>
</body></html>
);
}Context
When creating custom error pages in Next.js, especially when migrating from Pages to App Router
Revisions (0)
No revisions yet.