loading.js and not-found.js in Next.js — Streaming UI, Instant Feedback, and Graceful 404s

Next.js supports streaming UI with React Suspense using loading.js, and provides graceful error handling with not-found.js and global-not-found.js.. This article explains how to create instant loading states, define custom 404 pages, and optimize user experience and SEO with these special files.

loading.jsnot-found.jsglobal-not-found.jsSuspense

~3 min read • Updated Oct 29, 2025

1. loading.js — Instant Loading UI with Suspense


The loading.js file lets you show a fallback UI instantly while a route segment streams in. It wraps page.js and its children in a <Suspense> boundary.

// app/feed/loading.tsx
export default function Loading() {
  return <p>Loading...</p>
}

Best Practices:

  • Use lightweight UI like skeletons or spinners
  • Can be a Server or Client Component
  • Prefetched for fast navigation
  • Shared layouts stay interactive during loading

Manual Suspense Boundaries:

// app/dashboard/page.tsx
import { Suspense } from 'react'
import { PostFeed, Weather } from './Components'

export default function Posts() {
  return (
    <section>
      <Suspense fallback={<p>Loading feed...</p>}>
        <PostFeed />
      </Suspense>
      <Suspense fallback={<p>Loading weather...</p>}>
        <Weather />
      </Suspense>
    </section>
  )
}

2. SEO and Streaming Behavior


  • Metadata: Resolved before streaming begins
  • Status Code: Always 200 for streamed content
  • Soft 404s: Prevented using <meta name="robots" content="noindex">

3. not-found.js — Route-Level 404 UI


Use not-found.js to display a custom 404 when notFound() is called inside a route segment.

// app/not-found.tsx
import Link from 'next/link'

export default function NotFound() {
  return (
    <div>
      <h2>Not Found</h2>
      <p>Could not find requested resource</p>
      <Link href="/">Return Home</Link>
    </div>
  )
}

Streaming vs Non-Streaming:

  • Streamed: status 200 + noindex meta tag
  • Non-streamed: status 404

Async NotFound Example:

export default async function NotFound() {
  const headersList = await headers()
  const domain = headersList.get('host')
  const data = await getSiteData(domain)

  return (
    <div>
      <h2>Not Found: {data.name}</h2>
      <p>Could not find requested resource</p>
      <p><Link href="/blog">View all posts</Link></p>
    </div>
  )
}

4. global-not-found.js — App-Wide 404 Page


Use global-not-found.js to define a fallback 404 page for unmatched routes across your app.

// app/global-not-found.tsx
import './globals.css'
import { Inter } from 'next/font/google'

const inter = Inter({ subsets: ['latin'] })

export default function GlobalNotFound() {
  return (
    <html lang="en" className={inter.className}>
      <body>
        <h1>404 - Page Not Found</h1>
        <p>This page does not exist.</p>
      </body>
    </html>
  )
}

Enable in next.config.ts:

const nextConfig = {
  experimental: {
    globalNotFound: true,
  },
}

Use Cases:

  • Multiple root layouts (e.g. (admin), (shop))
  • Top-level dynamic segments (e.g. [country])

Conclusion


With loading.js, not-found.js, and global-not-found.js, Next.js gives you full control over streaming UI and graceful error handling. These features improve user experience, navigation speed, and SEO — making your app feel fast, responsive, and reliable.


Written & researched by Dr. Shahin Siami