global-error.js
Catches errors thrown by the root layout by replacing the entire document with its own markup.
'use client'
export default function GlobalError({
error,
retry,
}: {
error: Error & { digest?: string }
retry: () => void
}): React.ReactNode- Added in Next.js v13.1.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$error | Error & { digest?: string } | The thrown error forwarded to the client. As with error.js, Server Component errors are reduced to a generic message in production. |
$error.digestoptional | string | undefined | Generated hash of the thrown error, for matching against server-side logs. |
$retry | () => void | Re-fetches and re-renders the boundary's children. Stable since v16.3.0. |
$resetoptional | () => void | Clears the error state and re-renders without re-fetching. retry() is preferred in most cases. |
Return value
React.ReactNodeA complete HTML document — it must render its own <html> and <body> because it replaces the root layout.
Errors
metadata and generateMetadata exports are not supported here, because error boundaries must be Client Components; use React's <title> component instead.
Changelog
- PHP v16.3.0retry prop became stable.
- PHP v16.2.0unstable_retry prop added.
- PHP v15.2.0global-error is now also displayed in development, not only in production.
- PHP v13.1.0global-error introduced.
global-error lives in the root of the app directory and catches what nothing else can: an error thrown by the root layout or root template itself. Since it replaces that layout while active, it has to render <html> and <body> of its own, and import any styles or fonts it needs — nothing from your root layout is in scope.
It is the last line of defence, not a routine boundary. Put ordinary recovery UI in per-segment error.js files, where the surrounding chrome stays intact; use this only for the case where there is no surrounding chrome left.
What trips people up
- Omitting
<html>and<body>produces invalid markup — this file is the whole document. - Global styles do not reach it. Neither do fonts, nor an app-level theme class or
data-themeattribute set by your root layout. The default UI follows the OS colour scheme; apply your theme inside this component if you want it to match. metadataandgenerateMetadataare unavailable because error boundaries must be Client Components. Render React's<title>element instead.- Before 15.2 it only appeared in production, which is why so many people believed it was not working — development showed the error overlay instead.
- Keep it dependency-free. Anything it imports could be the very thing that just failed.
Example
'use client' // Error boundaries must be Client Components
export default function GlobalError({
error,
retry,
}: {
error: Error & { digest?: string }
retry: () => void
}) {
return (
// global-error must include html and body tags
<html>
<body>
<h2>Something went wrong!</h2>
<button onClick={() => retry()}>Try again</button>
</body>
</html>
)
}When to use it
- Show a branded crash screen when the root layout throws during render
- Give users a Try again button when the whole document failed to render
- Report catastrophic client-side render failures to an error tracker
- Keep a minimal fallback shell that depends on no application code
Frequently asked questions
Why is my global-error.js not showing in development?
Before Next.js 15.2 global-error rendered only in production; in development the error overlay took its place. On 15.2 and later it is displayed in development too. Also confirm the file sits at app/global-error.tsx, starts with 'use client', and that the error really originates in the root layout — segment errors are handled by the nearest error.js instead.
Why does global-error.js ignore my global CSS and theme?
Because it replaces the root layout entirely, so nothing that layout imported is in scope — stylesheets, fonts and any theme class or data-theme attribute it set. Import or inline whatever this page needs directly inside the component. The default UI follows the operating system colour scheme via prefers-color-scheme, not your app's theme.
What is the difference between error.js and global-error.js?
error.js wraps a single route segment and renders inside your layouts, so the surrounding navigation stays visible and only the failing subtree is replaced. global-error.js sits at the root of the app directory, catches errors from the root layout or template that no segment boundary can reach, and replaces the whole document — which is why it must render its own <html> and <body>.
More examples
Self-contained styling, title and theme
Everything the page needs is inline: the theme attribute, the font, the padding. The document title comes from React's <title> element because metadata exports are unavailable in a Client Component. Importing a shared stylesheet or design-system component here would risk failing for the same reason the app just did.
'use client'
export default function GlobalError({
error,
retry,
}: {
error: Error & { digest?: string }
retry: () => void
}) {
return (
<html lang="en" data-theme="light">
<body style={{ fontFamily: 'system-ui', padding: '2rem' }}>
<title>Something went wrong</title>
<h1>We hit an unexpected error</h1>
{error.digest ? <p>Reference: {error.digest}</p> : null}
<button onClick={() => retry()}>Try again</button>
</body>
</html>
)
}Reporting before rendering the fallback
The reporter is read off window rather than imported, so a broken module graph cannot stop the fallback rendering. The effect fires once per distinct error object; retry() then re-fetches and re-renders the tree, and the fallback disappears if the second attempt succeeds.
'use client'
import { useEffect } from 'react'
export default function GlobalError({
error,
retry,
}: {
error: Error & { digest?: string }
retry: () => void
}) {
useEffect(() => {
window.__errorReporter?.captureException(error)
}, [error])
return (
<html lang="en">
<body>
<h2>The application could not start.</h2>
<button onClick={() => retry()}>Reload</button>
</body>
</html>
)
}How it relates to the root layout
The non-null assertion throws for any visitor without a theme cookie. No error.js can catch it, because error boundaries do not wrap the layout in their own segment and this is the outermost one — app/global-error.tsx renders the whole document instead, which is why it supplies its own <html> and <body>.
// app/layout.tsx — a throw in here is what global-error catches
import { cookies } from 'next/headers'
export default async function RootLayout({
children,
}: {
children: React.ReactNode
}) {
const store = await cookies()
const theme = store.get('theme')!.value // throws if the cookie is absent
return (
<html lang="en" data-theme={theme}>
<body>{children}</body>
</html>
)
}
Discussion