global-error.js

Catches errors thrown by the root layout by replacing the entire document with its own markup.

Signature
'use client'

export default function GlobalError({
  error,
  retry,
}: {
  error: Error & { digest?: string }
  retry: () => void
}): React.ReactNode
  • Added in Next.js v13.1.0

Parameters

ParameterTypeDescription
$errorError & { 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.digestoptionalstring | undefinedGenerated hash of the thrown error, for matching against server-side logs.
$retry() => voidRe-fetches and re-renders the boundary's children. Stable since v16.3.0.
$resetoptional() => voidClears 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-theme attribute 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.
  • metadata and generateMetadata are 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

Example · typescript
'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.

Example · typescript
'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.

Example · typescript
'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>.

Example · typescript
// 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

  • Be the first to comment on this lesson.