webroad.online
  1. 1Web
  2. 2HTML
  3. 3CSS
  4. 4JavaScript
  5. 5TypeScript
  6. 6Git
  7. 7Tooling
  8. 8React
  9. 9State management
  10. 10Next.js
  11. 11Forms
  12. 12Data and backend
  13. 13SEO
  14. 14Tailwind CSS
  15. 15Animations
  16. 16Testing
  17. 17Architecture
Next.js · Lesson 6 of 12

Data fetching and streaming

Data directly in Server Components, in parallel, Suspense and params as a Promise.

Updated

What data fetching means in Next

Data fetching = getting the data (from a DB, from an API) a page needs. In the Next App Router, the default place is the Server Component: an async function that reads the data directly and renders the result.

export default async function Page() {
  const posts = await db.post.findMany()       // an ORM, SQL, fetch — anything
  return <PostList posts={posts} />
}

Compared with the classic React way (useEffect + fetch + useState): no loading state on the client, no intermediate API route, no exposed secrets, no client → server → DB waterfall.

Ways to get data — and when

Way Where When
a direct await in a Server Component the server the default — any data display
a Promise passed to a Client Component + use() the server starts it, the client reads it the client component needs data, but you want streaming
TanStack Query the client very interactive data (polling, infinite scroll) — Server state
a Route Handler the server an API for others / webhooks — not for your own pages
useEffect + fetch the client almost never

Parallel, not a cascade

A waterfall = requests that wait on each other for no reason.

// ✗ a waterfall — 2 × the time
const user = await getUser(id)
const posts = await getPosts(id)

// ✓ parallel
const [user, posts] = await Promise.all([getUser(id), getPosts(id)])

What streaming is

Without streaming, the user sees the page only when all the data is ready. With streaming, the server sends the HTML in pieces: what's ready shows immediately, the slow parts arrive when they finish.

The tool: <Suspense>.

export default function Page() {
  return (
    <>
      <Header />                                   {/* shows immediately */}
      <Suspense fallback={<Skeleton />}>
        <Recommendations />                        {/* async, shows when it's ready */}
      </Suspense>
    </>
  )
}

loading.tsx = an automatic <Suspense> around the whole page.

A Promise passed to the client

You start the request on the server, but read it in a Client Component:

// server
const statsPromise = getStats()                  // no await — doesn't block
<Suspense fallback={<Spinner />}>
  <Stats data={statsPromise} />
</Suspense>

// client
'use client'
import { use } from 'react'
export function Stats({ data }: { data: Promise<Stats> }) {
  const stats = use(data)                        // suspends until it arrives
}

Runtime data

cookies(), headers(), searchParams, params (for values that weren't pre-generated) depend on the request — they can't be known at build time. The components that read them become dynamic and must be wrapped in <Suspense>, so the rest of the page stays static. That's exactly how your progress is shown in this app.

Deduplication

The same function called from the layout + the page + generateMetadata?

Tool Lifetime
React's cache() for the duration of one request
'use cache' persistent, across requests — the Caching lesson

Summary

  • Data → directly in Server Components, with await.
  • Promise.all for independent requests.
  • <Suspense> for streaming; runtime data goes inside Suspense.

Official sources

Exercises

Was this page helpful?

One tap — no account needed.