new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Next.js · Guides · all subjects

data fetching & streaming

64 notes in this subject, read out of this brain and free to use. This is page 1 of 2.

Bots and crawlers receive fully rendered pages, not streams

Bots and crawlers are served differently from browsers. Next.js waits for data fetching to finish and sends the fully rendered page instead of streaming it progressively.

Server Components fetch data with async/await

Server Components can fetch data using any asynchronous I/O including the fetch API, ORMs, or database clients. Turn the component into an asynchronous function and await the fetch call or database query.

Identical fetch requests are memoized by default in React component tree

Multiple identical fetch requests in a React component tree are memoized by default, so you can fetch data in the component that needs it instead of drilling props.

fetch requests are not cached by default and block rendering

By default, fetch requests in Server Components are not cached and will block the page from rendering until the request is complete. Use the 'use cache' directive to cache results, or wrap the fetching component in Suspense to stream fresh data at request time.

ORM and database queries in Server Components are safe from client bundle exposure

Server Components are rendered on the server, so credentials and query logic will not be included in the client bundle. This allows you to safely make database queries using an ORM or database client without exposing sensitive information.

Streaming breaks pages into chunks for progressive rendering

Streaming breaks a page into smaller chunks and progressively sends those chunks from the server to the client. This improves initial load time and user experience when you have slow data requests.

loading.js streams entire page during data fetching

You can create a loading.js file in the same folder as your page to stream the entire page while the data is being fetched. On navigation, the user immediately sees the layout and loading state while the page is being rendered, then the new content is automatically swapped in once rendering is complete.

loading.js automatically wraps page in Suspense boundary

The loading.js file is nested inside layout.js and automatically wraps the page.js file and any children below in a Suspense boundary.

Uncached data in layout blocks navigation even with loading.js

A layout that accesses uncached or runtime data (such as cookies(), headers(), or uncached fetches) does not fall back to a same route segment loading.js. Instead, it blocks navigation until the layout finishes rendering. Use Suspense with a fallback to fix this, or move the data fetching into page.js where loading.js can cover it.

Use Suspense closer to runtime or uncached data access

While loading.js works well for streaming route segments, using Suspense closer to the runtime or uncached data access is recommended over loading.js.

Suspense allows granular control over page streaming

Suspense allows you to be more granular about what parts of the page to stream. You can immediately show any page content that falls outside the Suspense boundary and stream in specific components inside the boundary.

Create meaningful loading states for better UX

An instant loading state is fallback UI shown immediately to the user after navigation. For the best user experience, design loading states that are meaningful and help users understand the app is responding, such as skeletons, spinners, or meaningful parts of future screens like cover photos or titles.

React use API streams data from server to client

You can use React's use API to stream data from the server to a Client Component. Fetch data in your Server component without awaiting, and pass the promise as a prop to the Client Component. In the Client Component, use the use() API to read the promise.

use API must be wrapped in Suspense boundary

When using the use API to resolve a promise in a Client Component, the component must be wrapped in a Suspense boundary to display a fallback while the promise is being resolved.

Community libraries for Client Component data fetching

You can use community libraries like SWR or React Query to fetch data in Client Components. These libraries have their own semantics for caching, streaming, and other features.

Sequential data fetching blocks on dependent requests

Sequential data fetching happens when one request depends on data from another. Place one request after another using await, so the second request is blocked until the first resolves. Use Suspense to show fallback UI while dependent components load.

Parallel data fetching prevents blocking with Promise.all

To fetch data in parallel, initiate multiple requests by calling fetch without awaiting them immediately, then use Promise.all to await all results at once. Requests begin as soon as fetch is called, starting them in parallel rather than sequentially.

Use Promise.allSettled to handle partial failures in parallel requests

When using Promise.all with multiple requests, if one request fails the entire operation will fail. Use Promise.allSettled instead to handle cases where you want partial results even if some requests fail.

React.cache wraps data-fetching functions for request-scoped memoization

Wrap a data-fetching function in React.cache so multiple components in the same request share one result instead of refetching. This provides memoization scoped to the current request only, with no sharing between requests.

React.cache example with getUser function

import { cache } from 'react' export const getUser = cache(async () => { const res = await fetch('https://api.example.com/user') return res.json() }) Then Server Components can call getUser() directly and multiple calls within the same request return the same memoized result.

Parallel data fetching example with Promise.all

async function getArtist(username: string) { const res = await fetch(`https://api.example.com/artist/${username}`) return res.json() } async function getAlbums(username: string) { const res = await fetch(`https://api.example.com/artist/${username}/albums`) return res.json() } export default async function Page({ params, }: { params: Promise<{ username: string }> }) { const { username } = await params // Initiate requests const artistData = getArtist(username) const albumsData = getAlbums(username) const [artist, albums] = await Promise.all([artistData, albumsData]) return ( <> <h1>{artist.name}</h1> <Albums list={albums} /> </> ) }

Sequential data fetching example with Suspense

export default async function Page({ params, }: { params: Promise<{ username: string }> }) { const { username } = await params const artist = await getArtist(username) return ( <> <h1>{artist.name}</h1> <Suspense fallback={<div>Loading...</div>}> <Playlists artistID={artist.id} /> </Suspense> </> ) } async function Playlists({ artistID }: { artistID: string }) { const playlists = await getArtistPlaylists(artistID) return ( <ul> {playlists.map((playlist) => ( <li key={playlist.id}>{playlist.name}</li> ))} </ul> ) }

Suspense example with granular streaming

import { Suspense } from 'react' import BlogList from '@/components/BlogList' import BlogListSkeleton from '@/components/BlogListSkeleton' export default function BlogPage() { return ( <div> <header> <h1>Welcome to the Blog</h1> <p>Read the latest posts below.</p> </header> <main> <Suspense fallback={<BlogListSkeleton />}> <BlogList /> </Suspense> </main> </div> ) }

React use API example for streaming data to Client Component

Server component app/blog/page.tsx: import Posts from '@/app/ui/posts' import { Suspense } from 'react' export default function Page() { const posts = getPosts() return ( <Suspense fallback={<div>Loading...</div>}> <Posts posts={posts} /> </Suspense> ) } Client component app/ui/posts.tsx: 'use client' import { use } from 'react' export default function Posts({ posts, }: { posts: Promise<{ id: string; title: string }[]> }) { const allPosts = use(posts) return ( <ul> {allPosts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ) }

SWR example for Client Component data fetching

'use client' import useSWR from 'swr' const fetcher = (url) => fetch(url).then((r) => r.json()) export default function BlogPage() { const { data, error, isLoading } = useSWR( 'https://api.vercel.app/blog', fetcher ) if (isLoading) return <div>Loading...</div> if (error) return <div>Error: {error.message}</div> return ( <ul> {data.map((post: { id: string; title: string }) => ( <li key={post.id}>{post.title}</li> ))} </ul> ) }

Server Component fetch example with fetch API

export default async function Page() { const data = await fetch('https://api.vercel.app/blog') const posts = await data.json() return ( <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ) }

Server Component data fetching with ORM example

import { db, posts } from '@/lib/db' export default async function Page() { const allPosts = await db.select().from(posts) return ( <ul> {allPosts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ) }

Ensure requests are authenticated and authorized

When fetching data in Server Components, especially with ORMs or database clients, ensure requests are properly authenticated and authorized. See the data security guide for best practices on securing server-side data access.

Three client data-fetching patterns

Client data-fetching libraries support three patterns: Inline loading states (SWR with useSWR, TanStack Query with useQuery) where each component renders its own loading UI and data becomes available after hydration; Suspense loading states (SWR with suspense: true, TanStack Query with useSuspenseQuery) where loading UI is defined at a boundary and data becomes available after hydration; Provided by the server (SWR with <SWRConfig fallback>, TanStack Query with <HydrationBoundary>) where data becomes available in the initial render or streamed from the server.

When to avoid client data-fetching libraries

If a Client Component only needs to read server data once and data never revalidates on the client, pass it a Promise and unwrap it with React's use() API. This avoids adding a library for simple read-once scenarios.

Client data-fetching libraries and their use cases

Use a client data-fetching library such as SWR, TanStack Query, or Apollo Client when Client Components need a shared browser cache. These libraries provide focus revalidation, interval polling, request deduplication, and optimistic updates across components.

When to use Suspense for client data-fetching

Use Suspense to define loading UI at a boundary and coordinate which parts of the interface reveal together or progressively. Suspense coordinates rendering, while the data library and component structure determine when requests start.

Browser-driven interactions and client-only fetching

For browser-driven interactions such as autocomplete, you can use either inline or Suspense client-only fetching pattern. The initial result waits for hydration and a browser request, which is often the right tradeoff for data that is not needed until an interaction.

Providing initial data from Server Components

Provide initial data from a Server Component when the server knows what the initial render needs. The value can be included in the initial render or streamed through Suspense. The library receives it in the React Server Component payload and can continue managing it in the browser.

Server Components, data-fetching libraries, and mutations roles

Server Components provide the initial data scoped to the segment that owns it. The data-fetching library stores the browser value under a shared cache identity. Mutations can update the browser cache immediately and invalidate cached server data so the next render can read a fresh value.

SWR: provide initial data from Server Component with fallback

With SWR 2.3.0 and React 19, a Server Component can provide fallback data before the client takes over using the server-provided data pattern. Wrap `<SWRConfig>` around the Client Component and pass `fallback` with the SWR key pointing to a Promise (not awaited). Only components that read the key will suspend. The fallback and Client Component must use exactly the same SWR key or SWR will ignore the fallback and fetch on the client.

SWR fallback key must match useSWR key exactly

The `fallback` key and the `useSWR` key must match exactly. If they drift, SWR ignores the fallback value and fetches on the client.

SWR mutate with optimistic updates example

Example showing SWR mutation pattern: ```tsx const { mutate } = useSWRConfig() return mutate( activityCache.key, async () => { await markActivityReadAction() return { count: 0 } }, { optimisticData: { count: 0 }, revalidate: false, rollbackOnError: true, throwOnError: false, } ) ``` Pass optimisticData to show the value immediately, set revalidate: false to skip revalidation after the write, rollbackOnError: true to restore previous value on failure, and throwOnError: false to prevent throwing.

SWR Route Handler pattern for fallback and revalidation

The SWR key points to a Route Handler with a `GET` method. The Route Handler can call the same function that provides the fallback, while the browser uses the URL for revalidation and polling. This allows sharing the same data source between server fallback and client-side revalidation.

SWR: refreshInterval for scheduled revalidation

To refresh data on a schedule, set the `refreshInterval` option in useSWR. This enables polling at specified intervals. See SWR documentation on revalidation for details.

SWR fetcher function pattern with error handling

Define a fetcher function that takes a URL and returns data or throws an error. Example: ```ts async function fetcher(url: string): Promise<Product[]> { const response = await fetch(url) if (!response.ok) throw new Error('Failed to fetch products') return response.json() } ``` Pass this to `useSWR` as the second argument. SWR handles retries and error states.

SWR params.then() pattern for dynamic routes

Use `params.then()` to handle async route parameters. Wrap component logic inside the promise to keep the fallback visible until route parameters resolve. Then create a separate, unawaited Promise for the SWR fallback that provides data based on the resolved params.

SWR: fetch data on client with conditional key

Use `useSWR` when the component should render its own loading and error states. A conditional key delays the request until interaction provides an input. Pass `null` as the key when the query is empty to prevent requests, and the hook will return `data = []` by default. Handle error and isLoading states conditionally.

SWR fallback freshness and revalidateIfStale option

By default, SWR treats fallback data as stale and starts a browser revalidation after hydration. SWR does not provide a time-based freshness window for fallback data. Setting `revalidateIfStale: false` skips revalidation when the hook mounts with cached data. This setting applies to every mount, unlike TanStack Query's `staleTime`. Focus, reconnect, polling, and `mutate` can still revalidate the key.

SWR: use Suspense for client data with suspense: true option

Use `suspense: true` when the nearest Suspense boundary should define the loading UI. Keep the interactive shell outside the boundary so it remains available while results load. With an unconditional key, SWR defines `data` after Suspense resolves. Handle request errors with the nearest error boundary.

SWR isLoading vs isValidating distinction

The `isLoading` value is `true` when a request is running and there is no loaded data to display. The `isValidating` value is `true` whenever a request is running, including background revalidation. With `suspense: true`, use `isValidating` to provide background refresh feedback during revalidation.

SWR Suspense: network waterfalls with parallel vs sequential reads

Independent Suspense reads can start in parallel when they render in sibling components. Multiple Suspense reads in one component run sequentially, creating network waterfalls.

TanStack Query isFetching provides background refresh feedback

Use the isFetching property from useQuery or useSuspenseQuery to detect when a background refetch is occurring. After a query has data, later refetches keep the cached data rendered, and isFetching allows the UI to indicate a refresh is in progress.

Example: useQuery with enabled option for delayed requests

import { useQuery } from '@tanstack/react-query'; async function searchProducts(query: string) { const response = await fetch(`/api/products?query=${encodeURIComponent(query)}`); if (!response.ok) throw new Error('Failed to fetch products'); return response.json(); } export function ProductAutocomplete({ query }: { query: string }) { const { data = [], error, isPending } = useQuery({ queryKey: ['product-search', query], queryFn: () => searchProducts(query), enabled: query.length > 0, }); if (!query) return null; if (error) return <p>Failed to load products.</p>; if (isPending) return <p>Loading products...</p>; return <ul>{data.map(p => <li key={p.id}>{p.name}</li>)}</ul>; } This example shows using useQuery with an enabled option that delays the request until the query string has content.

Interactive shell stays available during Suspense boundary loading

Keep the interactive shell (like a search input) outside the Suspense boundary so it remains available while results load. Place the boundary only around the part that depends on fetched data.

Example: useSuspenseQuery with Suspense boundary

import { useSuspenseQuery } from '@tanstack/react-query'; import { Suspense } from 'react'; async function searchProducts(query: string) { const response = await fetch(`/api/products?query=${encodeURIComponent(query)}`); if (!response.ok) throw new Error('Failed to fetch products'); return response.json(); } export function ProductAutocomplete({ query }: { query: string }) { if (!query) return null; return ( <Suspense fallback={<p>Loading products...</p>}> <ProductResults query={query} /> </Suspense> ); } function ProductResults({ query }: { query: string }) { const { data } = useSuspenseQuery({ queryKey: ['product-search', query], queryFn: () => searchProducts(query), }); return <ul>{data.map(p => <li key={p.id}>{p.name}</li>)}</ul>; } This example shows using useSuspenseQuery with the boundary defining the loading UI, keeping the interactive shell outside the boundary.

Example: Shared query cache contract with queryOptions

import { queryOptions } from '@tanstack/react-query'; export type Product = { id: string; name: string }; export const productCache = { key: (id: string) => ['product', id] as const, options: (id: string) => queryOptions({ queryKey: productCache.key(id), queryFn: async (): Promise<Product> => { const res = await fetch(`/api/products/${id}`); if (!res.ok) throw new Error('Failed to fetch product'); return res.json(); }, staleTime: 30_000, }), }; This example shows keeping query key and options in a shared contract so both server and client reference the same cache identity.

Example: Server-provided initial data with dehydration

import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'; import { getProduct } from './data'; import { productCache } from './product-cache'; function ProductData({ id }: { id: string }) { const queryClient = new QueryClient(); void queryClient.prefetchQuery({ ...productCache.options(id), queryFn: () => getProduct(id), }); return ( <HydrationBoundary state={dehydrate(queryClient, { shouldDehydrateQuery: (query) => defaultShouldDehydrateQuery(query) || query.state.status === 'pending', })} > <ProductView id={id} /> </HydrationBoundary> ); } This example shows prefetching without awaiting and providing dehydrated state to HydrationBoundary with pending query support.

Example: useMutation with optimistic updates and error recovery

'use client'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { markActivityReadAction } from './actions'; import { activityCache } from './activity-cache'; export function MarkReadButton() { const queryClient = useQueryClient(); const queryKey = activityCache.key; const markRead = useMutation({ mutationFn: markActivityReadAction, onMutate: async () => { await queryClient.cancelQueries({ queryKey }); const previous = queryClient.getQueryData(queryKey); queryClient.setQueryData(queryKey, { count: 0 }); return { previous }; }, onError: (_error, _variables, context) => { queryClient.setQueryData(queryKey, context?.previous); }, }); return <button onClick={() => markRead.mutate()}>Mark read</button>; } This example shows optimistic updates with error recovery, canceling pending refetches and restoring previous data if the mutation fails.

TanStack Query provider setup for server and client

Create a new QueryClient for each server render and reuse one query client in the browser. Check if window is undefined to detect server context: on the server, return a new QueryClient(); in the browser, create the client once and store it in a variable using the nullish coalescing operator. Wrap routes in QueryClientProvider from '@tanstack/react-query'.

useQuery for client-side data fetching with loading states

Use useQuery from '@tanstack/react-query' when the component should render its own loading and error states. The hook accepts a config object with queryKey, queryFn, and optional enabled option. It returns an object with data (defaulting to an empty array), error, and isPending properties. The enabled option delays the request until a condition is met, such as user input length validation.

useSuspenseQuery for Suspense-based client data fetching

Use useSuspenseQuery from '@tanstack/react-query' when the nearest Suspense boundary should define the loading UI. The hook throws a promise during initial fetch, caught by the boundary. It returns data directly without isPending or error properties. If the initial request fails, useSuspenseQuery propagates the error to the nearest error boundary. After a query has data, later refetches keep the cached data rendered instead of showing the Suspense fallback again.

Prevent request waterfalls with TanStack Query

Multiple useSuspenseQuery calls in one component run sequentially, creating request waterfalls. Place independent queries in sibling components instead, or use useSuspenseQueries to run them in parallel.

TanStack Query 5.40.0+ dehydration of pending queries

TanStack Query 5.40.0 or later can dehydrate pending queries. Start prefetchQuery without awaiting it so rendering is not blocked, then pass the dehydrated state to HydrationBoundary. Call dehydrate() with shouldDehydrateQuery option set to a function that returns defaultShouldDehydrateQuery(query) || query.state.status === 'pending' to include pending queries in the hydration state.

Shared query cache contract between server and client

Server and Client Components must use the same query key. Keep the key and query options together in a shared module using queryOptions() from '@tanstack/react-query'. Both the server's prefetchQuery and the client's useSuspenseQuery must reference the same options object or at least the same queryKey to ensure cache consistency.

Give your agent this brain