File Conventions — loading, error, not-found

Filenames That Build Up the UX

File Conventions — loading, error, not-found

Special filenames create UI boundaries. loading.tsx suspends UI, error.tsx catches errors, and not-found.tsx renders when notFound() is called.

4 min read Level 2/5 #nextjs#conventions#error
What you'll learn
  • Use `loading.tsx` for instant skeletons
  • Use `error.tsx` as an error boundary
  • Use `not-found.tsx` for 404s

Next.js turns a handful of specially named files into UI boundaries. You write the component; Next.js wires up the Suspense and Error Boundary plumbing.

loading.tsx — Instant Skeleton

A loading.tsx next to a page.tsx automatically wraps the page in <Suspense>. The fallback is shown until the server component finishes rendering.

// app/dashboard/loading.tsx
export default function Loading() {
  return <div className="skeleton">Loading dashboard…</div>;
}

You do not import or wire this anywhere — the filename is the wiring.

error.tsx — Segment Error Boundary

An error.tsx becomes the React error boundary for its segment. It must be a Client Component because it uses hooks like useEffect for logging and reset for retry.

// app/dashboard/error.tsx
'use client';
import { useEffect } from 'react';

export default function DashboardError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    console.error(error);
  }, [error]);

  return (
    <div>
      <h2>Something went wrong.</h2>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}

not-found.tsx — Friendly 404

When a server component calls notFound(), Next.js renders the closest not-found.tsx instead of the page.

// app/blog/[slug]/not-found.tsx
export default function PostNotFound() {
  return <h2>That post does not exist.</h2>;
}
// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';

export default async function PostPage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const post = await fetchPost(slug);
  if (!post) notFound();
  return <article>{post.title}</article>;
}

Catastrophic Failures

If even the root layout crashes, app/global-error.tsx takes over. It must render its own <html> and <body>. Most apps never need to define one — but knowing it exists is useful.

template.tsx — Fresh Per Navigation →