Free tutorials & notes in Hindi & English · Clean code examples 🧰 Free PDF & Image Tools →
🔵 App Router – Pages, Layouts & Navigation  ·  Lesson 14

loading, error & not-found Pages

Three special files

  1. 1loading.jsx — shown while the page is fetching data
  2. 2error.jsx — shown if the page throws an error
  3. 3not-found.jsx — shown for unknown URLs or notFound()
Put them in any route folder; they apply to that folder and below

loading.jsx — instant loading state

If a page waits for a slow database, the user sees a blank screen. Add loading.jsx next to the page and Next.js shows it immediately, then replaces it with the real page.

JSX – app/blog/page.jsx (slow)
// app/blog/page.jsx — simulate a slow database
async function getPosts() {
  await new Promise((r) => setTimeout(r, 2000))   // 2 seconds
  return [{ slug: 'hello', title: 'Hello Next.js' }]
}

export default async function Blog() {
  const posts = await getPosts()
  return posts.map((p) => <h2 key={p.slug}>{p.title}</h2>)
}
JSX + CSS – app/blog/loading.jsx skeleton
// app/blog/loading.jsx
export default function Loading() {
  return (
    <div className="container" aria-busy="true">
      {[1, 2, 3].map((i) => (
        <div key={i} className="skeleton-card">
          <div className="sk sk-title" />
          <div className="sk sk-line" />
          <div className="sk sk-line short" />
        </div>
      ))}
    </div>
  )
}

/* globals.css */
.sk { background: linear-gradient(90deg,#eee 25%,#f5f5f5 50%,#eee 75%);
      background-size: 200% 100%; animation: shine 1.2s infinite; border-radius: 8px; }
.sk-title { height: 22px; width: 60%; margin-bottom: 10px; }
.sk-line  { height: 12px; margin-bottom: 8px; }
.sk-line.short { width: 40%; }
@keyframes shine { to { background-position: -200% 0; } }
Click Blog0 msloading.jsxskeleton showsgetPosts()2000 mspage.jsxreal posts
The user never stares at a frozen screen
🔒 localhost:3000/blogEduSiteBlogHome▇▇▇▇▇▇▇▇▇▇▇▇▇▇ ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ ▇▇▇▇▇Skeleton cards while data loads

Suspense for part of a page

JSX – app/page.jsx
// app/page.jsx — hero appears instantly, reviews stream in later
import { Suspense } from 'react'

async function Reviews() {
  const res = await fetch('https://api.example.com/reviews')   // slow
  const reviews = await res.json()
  return <ul>{reviews.map((r) => <li key={r.id}>⭐ {r.text}</li>)}</ul>
}

export default function Home() {
  return (
    <main>
      <h1>Learn coding with EduSite</h1>          {/* instant */}
      <Suspense fallback={<p>Loading reviews…</p>}>
        <Reviews />                               {/* streams later */}
      </Suspense>
    </main>
  )
}
BrowserNext.jsReviews APIGET /HTML: hero + "Loading reviews…"fetch reviewsreviews JSONstream reviews HTML (same response)
Streaming: the fast part arrives first

error.jsx — catch crashes

JSX – app/error.jsx
// app/error.jsx  (from the EduSite project)
'use client' // error boundaries must be Client Components

export default function Error({ error, reset }) {
  return (
    <div className="container section" style={{ textAlign: 'center' }}>
      <h1>Something went wrong</h1>
      <p className="lead">Please try again. If the problem continues, contact us.</p>
      <button className="btn" onClick={() => reset()}>Try again</button>
    </div>
  )
}

reset() tries to render the segment again — useful when a network call failed for a moment. Only the broken segment shows the error; the header and footer from the layout still work.

global-error.jsx

JSX – app/global-error.jsx
// app/global-error.jsx — catches errors in the ROOT layout itself
'use client'
export default function GlobalError({ reset }) {
  return (
    <html><body>
      <h1>Site is temporarily unavailable</h1>
      <button onClick={() => reset()}>Reload</button>
    </body></html>
  )
}

not-found.jsx — real 404

JSX – app/not-found.jsx
// app/not-found.jsx  (from the EduSite project)
import Link from 'next/link'

export default function NotFound() {
  return (
    <div className="container section" style={{ textAlign: 'center' }}>
      <h1>404 – Page not found</h1>
      <p className="lead">The page you are looking for does not exist or has moved.</p>
      <Link href="/" className="btn">Go to home page</Link>
    </div>
  )
}
// Shown for unknown URLs AND whenever you call notFound()
Custom 404 page in Next.js
EduSite 404 page (real screenshot, HTTP status 404)

How Next.js wraps your page

Component hierarchy (conceptual)
<Layout>
  <ErrorBoundary fallback={<Error />}>          ← error.jsx
    <Suspense fallback={<Loading />}>          ← loading.jsx
      <ErrorBoundary fallback={<NotFound />}>   ← not-found.jsx
        <Page />                                ← page.jsx
      </ErrorBoundary>
    </Suspense>
  </ErrorBoundary>
</Layout>
layout.jsxalways visibleerror.jsx boundarycatches crashes belowloading.jsx (Suspense)fallback while waitingpage.jsxyour content
Special files nest around the page

Common mistakes

  • Forgetting 'use client' in error.jsx → build error.
  • Expecting error.jsx to catch errors in the same folder's layout → it catches the page and children; layout errors go to the parent's error.jsx or global-error.jsx.
  • Showing a spinner for 50 ms data → unnecessary flicker; use loading UI where data is genuinely slow.
  • Putting loading.jsx above a dynamic [slug] route → the response starts streaming with status 200, so notFound() inside the page can no longer send a 404. For SEO pages, keep loading.jsx out of that path or set dynamicParams = false.

Summary

  • loading.jsx = automatic Suspense fallback; <Suspense> for one slow part.
  • error.jsx (client) catches errors and offers reset().
  • not-found.jsx + notFound() = real 404 pages.
✅ Practice

Add a 2-second delay to your blog page and build a 3-card skeleton loader. Then throw an error on purpose (throw new Error('test')) and see your error.jsx with a working "Try again" button.

Frequently Asked Questions

Why must error.jsx be a Client Component?
Error boundaries use React state on the browser to catch errors and offer a reset() retry, so the file must start with 'use client'.
Does loading.jsx hurt SEO?
No. Next.js streams the HTML; search engines receive the final content in the same response. The loading UI only helps users on slow connections.
What is the difference between loading.jsx and ?
loading.jsx wraps the whole page segment in Suspense automatically. A manual lets you show a fallback for only one slow part while the rest of the page appears immediately.
← Next.js Tutorial
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

Next topicCSS Modules & Global CSS Your progress: 14 / 37 (38%) · Lesson 15 / 37 Continue →

How was this lesson?

Your feedback helps us make the content better.

Write a review or suggestion (optional)

After approval, your name and city may appear publicly with your review. Please do not write your email or phone number.

▶ CodingEasily on YouTube

Learn with video lessons in easy Hindi

Featured video: RDBMS Part 3 – Class XI Commerce IT

▶ Subscribe – it’s free