Skip to Content
Nextjs3.1 App Router Data Fetching

App Router Data Fetching 🍱

Next.js App Router treats data as a first-class citizen. Server components can await data directly, while built-in routes manage loading, errors, and not-found states.

Server Component Fetching 🧑‍🍳

// app/dashboard/page.tsx async function getStats() { const res = await fetch('https://api.example.com/stats', { cache: 'no-store' }); if (!res.ok) throw new Error('Failed'); return res.json(); } export default async function Dashboard() { const stats = await getStats(); return <StatsGrid stats={stats} />; }
  • You can await inside server components because they run on the server.
  • Functions can live in the same file or be imported utilities.
  • Fetching server-side keeps secrets (API keys) safe and reduces bundle size.

fetch() Semantics ⚙️

Next.js wraps fetch to enable auto caching.

OptionBehavior
defaultCached per request URL + headers; reused across renders
{ cache: 'no-store' }Always revalidate (dynamic)
{ next: { revalidate: 60 } }ISR-like caching; revalidate every 60s
{ next: { tags: ['posts'] } }Tag-based invalidation with revalidateTag('posts')
  • Identical fetch calls in the same request dedupe automatically.
  • Use headers/cookies in fetch options to scope caching per user.

Streaming + Suspense 🌊

import { Suspense } from 'react'; const Feed = async () => { const posts = await getPosts(); return <PostList posts={posts} />; }; export default function Page() { return ( <Suspense fallback={<Skeleton />}> {/* Feed renders when data arrives */} <Feed /> </Suspense> ); }
  • Server components inside Suspense start streaming HTML as soon as they resolve.
  • Compose multiple Suspense boundaries for granular skeletons.

loading.tsx ⏳

  • Place app/dashboard/loading.tsx to show UI while the route (or nested segment) is loading.
  • Automatically displayed until the server segment resolves.
export default function Loading() { return <SkeletonGrid />; }

error.tsx 🚨

  • app/dashboard/error.tsx catches errors thrown in the matching segment.
  • Must be a client component ('use client') because it can offer retry buttons.
'use client'; export default function Error({ error, reset }) { return ( <div> <p>Oops: {error.message}</p> <button onClick={() => reset()}>Try again</button> </div> ); }

not-found.tsx 🚫

  • Rendered when notFound() is called or data fetch returns a 404-like state.
  • Place not-found.tsx alongside page.tsx to customize.
import { notFound } from 'next/navigation'; if (!product) notFound();

not-found.tsx might show a search box or helpful links.

Debugging Tips 🧭

  • Use next dev --turbo to view fetch logs (cache hits/misses).
  • Check the terminal output after next build—Next.js reports which routes are static vs dynamic.
  • Use export const revalidate = 0; for entire route segments that must always be dynamic.

Analogy: App Router data fetching is a conveyor belt—fetch results arrive on server trays, loading.tsx shows placeholder plates, error.tsx handles dropped trays, and not-found.tsx politely tells guests when an item is out of stock.

Last updated on