C.W.K.
Stream
Lesson 09 of 10 · published

Loading, Error & Not Found

~22 min · loading, error, not-found, Suspense

Level 0Curious
0 XP0/68 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

Three special files, three behaviors

FileWhat it doesServer or Client?
loading.tsxWraps the page in Suspense; shown while data loadsEither
error.tsxError boundary for the segment and belowMust be Client
not-found.tsxUI for notFound() calls in this segmentEither

global-error.tsx for the root

An error.tsx can't catch errors in its own layout. The root layout has a special boundary: app/global-error.tsx. It must render its own <html> and <body> because it replaces the broken root layout.

Trigger 404 from anywhere

Call notFound() from next/navigation in a Server Component to bail out and render the nearest not-found.tsx. The framework handles the status code (404) for you.

Code

Loading skeleton wired automatically·tsx
// app/dashboard/loading.tsx
export default function Loading() {
  return (
    <div className="animate-pulse space-y-3 p-6">
      <div className="h-6 w-1/3 rounded bg-gray-200" />
      <div className="h-4 w-full rounded bg-gray-200" />
      <div className="h-4 w-5/6 rounded bg-gray-200" />
    </div>
  );
}

// app/dashboard/page.tsx — slow data triggers the boundary
export default async function Dashboard() {
  const data = await fetchSlow();
  return <DashboardContent data={data} />;
}
Per-segment error boundary·tsx
// app/dashboard/error.tsx
'use client';
import { useEffect } from 'react';

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    // ship to Sentry / your logger
    console.error(error);
  }, [error]);

  return (
    <div className="p-6 text-center">
      <h2 className="text-lg font-semibold">Something went wrong</h2>
      <p className="text-sm text-gray-500 mt-1">{error.message}</p>
      <button
        onClick={() => reset()}
        className="mt-4 rounded bg-blue-600 px-4 py-2 text-white"
      >
        Try again
      </button>
    </div>
  );
}
404 from inside a Server Component·tsx
import { notFound } from 'next/navigation';

export default async function Page({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const post = await getPost(id);
  if (!post) notFound();
  return <Article post={post} />;
}

External links

Exercise

Add loading.tsx, error.tsx, and not-found.tsx to a route. Throw an error inside the page and confirm the error boundary catches it; call notFound() for an unknown id and confirm the not-found UI shows with a 404 status in the network tab.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.