Course outline · 0% complete

0/29 lessons0%

Course overview →

Loading and error states

lesson 4-3 · ~9 min · 13/29

loading.js: the instant fallback

Suppose app/blog/page.js takes two seconds to fetch its data. Without help the user sits looking at the old page, frozen, with no sign that anything is happening. Next.js fixes this with another special file:

// app/blog/loading.js
export default function Loading() {
  return <p>Loading posts…</p>;
}

Drop it next to page.js and Next.js shows it immediately on navigation, then streams in the real page when the data resolves. Under the hood this is React's Suspense mechanism, wired up for you by the file convention. In the React course you managed loading flags by hand. Here the router does it for the whole page.

click~0 msdata readyloading.jsreal page.jsthe skeleton shows instantly, the page streams in when its data resolves
With loading.js in place, navigation shows the fallback immediately while the async page renders, then the finished content streams in.

error.js: the safety net

When a page throws (the API is down, the JSON is malformed), the matching error.js renders instead of a crash:

// app/blog/error.js
"use client";

export default function Error({ error, reset }) {
  return (
    <div>
      <p>Something went wrong.</p>
      <button onClick={reset}>Try again</button>
    </div>
  );
}

Two details to decode:

  • error.js must be a client component. It needs the onClick for reset, which re-attempts the render.
  • Like layouts, both files scope to their folder: an error.js in app/blog/ catches blog failures without touching the rest of the site.

Why error.js must be a client component

The error.js convention is the one special file that always carries "use client", and the reason is interactivity.

An error boundary exists to offer recovery, and that recovery is typically a reset button with an onClick handler. Event handlers only exist in client components, as Unit 3 established, so a server-rendered error boundary could display a message but never give the user a way out. Rather than leave that as a trap, the convention simply requires error.js to be a client component.

Adding a skeleton to a slow dashboard

A /dashboard page fetches slowly and users see nothing during navigation. The file that fixes it is app/dashboard/loading.js, exporting a skeleton component.

Next.js shows that component the instant navigation starts, then swaps in the real page when the dashboard's async work finishes. No manual loading-state flags are involved, unlike the useEffect pattern from the React course where you tracked isLoading yourself.

Why it works out that way

  • The file sits next to page.js in the same folder, and the router pairs them automatically.
  • The convention is named after what the user is doing while they wait, which makes it easy to remember.
  • Because it scopes to the folder, a slow dashboard gets its own skeleton without affecting how the rest of the site loads.