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

building/streaming

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

Streaming route via loading.js

To fix prerender-blocking errors by streaming, add a loading.js file to wrap the whole segment in a <Suspense> boundary. Next.js prerenders the fallback as the route's static shell, while the params and data work runs at request time. This converts a route to ◐ Partial Prerender, serving the prerendered shell instantly then streaming page content.

Client Components with dynamic import are SSR'd by default

When using React.lazy() and Suspense, Client Components will be prerendered (SSR) by default.

ssr option for disabling prerendering in dynamic imports

The ssr option can be set to false in dynamic() to disable prerendering for a Client Component. Example: const ComponentC = dynamic(() => import('../components/C'), { ssr: false }). The ssr option only works for Client Components.

ssr option not supported in Server Components

The ssr: false option is not supported in Server Components and will cause an error. ssr: false is not allowed with next/dynamic in Server Components; it must be moved into a Client Component.

Dynamic import of Server Components only lazy-loads child Client Components

If you dynamically import a Server Component, only the Client Components that are children of the Server Component will be lazy-loaded, not the Server Component itself. It also helps preload static assets such as CSS when used in Server Components.

Automatic code splitting not supported for dynamic imports in Server Components

When a Server Component dynamically imports a Client Component, automatic code splitting is currently not supported.

Loading component option in dynamic import

The dynamic() function accepts a loading option to provide a custom loading component that will be rendered while the component is loading. Example: dynamic(() => import('../components/Component'), { loading: () => <p>Loading...</p> })

Dynamically import named exports

To dynamically import a named export, you can return it from the Promise returned by the import() function. Example: const ClientComponent = dynamic(() => import('../components/hello').then((mod) => mod.Hello))

Loading external libraries on demand with import()

External libraries can be loaded on demand using the dynamic import() function. This allows modules to be loaded in the browser only when needed, such as after a user interaction.

webpackIgnore and turbopackIgnore magic comments

Magic comments webpackIgnore and turbopackIgnore can be used with dynamic import() to skip bundling a dynamic import. The import expression will be left as-is in the output, useful for runtime-only modules. Example: const runtime = await import(/* webpackIgnore: true */ 'runtime-module')

turbopackOptional magic comment

The turbopackOptional magic comment suppresses build errors when a module might not exist. The import will still throw at runtime if the module is missing. Example: const feature = await import(/* turbopackOptional: true */ './optional-feature'). This is useful for conditional features that may not be installed, plugin systems where modules are optional, and gradual migrations where some files may not exist yet.

Lazy loading improves initial loading performance

Lazy loading in Next.js helps improve the initial loading performance of an application by decreasing the amount of JavaScript needed to render a route. It allows you to defer loading of Client Components and imported libraries, and only include them in the client bundle when they're needed.

Magic comments only work with dynamic expressions

Magic comments do not work with static import statements (import x from 'y'). They only work with dynamic expressions like import(), require(), require.resolve(), and new Worker().

webpackOptional not supported, use turbopackOptional instead

webpackOptional is not supported. Use turbopackOptional instead when using Turbopack.

dynamic() must be top-level for preloading to work

dynamic() can't be used inside of React rendering as it needs to be marked in the top level of the module for preloading to work, similar to React.lazy().

Two ways to implement lazy loading in Next.js

There are two ways to implement lazy loading in Next.js: using Dynamic Imports with next/dynamic, or using React.lazy() with Suspense.

next/dynamic is a composite of React.lazy and Suspense

next/dynamic is a composite of React.lazy() and Suspense. It behaves the same way in the app and pages directories to allow for incremental migration.

Server Components are automatically code split

By default, Server Components are automatically code split, and you can use streaming to progressively send pieces of UI from the server to the client. Lazy loading applies to Client Components.

Dynamic import path must be explicitly written

In import('path/to/component'), the path must be explicitly written. It can't be a template string or a variable. Furthermore the import() has to be inside the dynamic() call for Next.js to be able to match webpack bundles to the specific dynamic() call and preload them before rendering.

Use Streaming to prevent route blocking

Streaming should be used with Loading UI and React Suspense to progressively send UI from the server to the client and prevent the whole route from blocking while data is being fetched.

Example: partial prerendering with Suspense

```tsx export default async function Page() { return ( <> <Suspense fallback={<PromotionSkeleton />}> <PromotionContent /> </Suspense> <Header /> <ProductList /> </> ) } ``` A page that uses Suspense to stream dynamic content while serving static and cached content instantly from a CDN.

Stream components with request-specific data

When a component depends on request-specific inputs like the user's location or A/B tests that vary between users, caching won't work. Instead, streaming with Suspense is the right choice to unblock the response and allow the rest of the page to render.

Suspense boundaries for streaming

Suspense boundaries tell Next.js where to slice the streamed response into chunks and what fallback UI to show while content loads. The fallback is prerendered alongside static and cached content. The inner component streams in later once its async work completes, unblocking the response.

End-to-end streaming infrastructure requirements

To support streaming end-to-end in self-hosted Next.js: load balancers must support chunked transfer encoding or HTTP/2 streaming (some cloud load balancers like AWS ALB with Lambda integration may buffer responses by default), reverse proxies between the load balancer and Next.js must pass through chunked responses without buffering, and if using Partial Prerendering, streaming support is required (without it, the static shell and dynamic content are delivered together after the full render completes, eliminating PPR's time-to-first-byte advantage).

Streaming support required for self-hosted nginx

The Next.js App Router supports streaming responses when self-hosting. If using nginx or a similar proxy, you need to configure it to disable buffering to enable streaming. For nginx, set the X-Accel-Buffering header to 'no'.

X-Accel-Buffering header configuration for nginx

Example configuration in next.config.js to disable buffering in nginx: ```js module.exports = { async headers() { return [ { source: '/:path*{/}?', headers: [ { key: 'X-Accel-Buffering', value: 'no', }, ], }, ] }, } ```

Dynamic import with ssr false example

import dynamic from 'next/dynamic'; const ClientOnlyComponent = dynamic(() => import('./component'), { ssr: false });

Render components only in browser with next/dynamic

Client components are prerendered during next build. To disable prerendering for a Client Component and only load it in browser environment, use next/dynamic with ssr: false option. This is useful for third-party libraries that rely on browser APIs like window or document. You can also add a useEffect that checks for existence of these APIs, and if they do not exist, return null or a loading state which would be prerendered.

What is a strict SPA definition

A strict Single-Page Application is defined by two characteristics: (1) Client-side rendering (CSR) where the app is served by one HTML file (e.g. index.html) and every route, page transition, and data fetch is handled by JavaScript in the browser, and (2) No full-page reloads where client-side JavaScript manipulates the current page's DOM and fetches data as needed rather than requesting a new document for each route. Strict SPAs often require large amounts of JavaScript to load before the page can be interactive, and client data waterfalls can be challenging to manage.

Why Next.js improves SPAs

Next.js addresses SPA limitations by: (1) automatically code splitting JavaScript bundles and generating multiple HTML entry points into different routes to avoid loading unnecessary code on the client-side, reducing bundle size and enabling faster page loads; (2) the next/link component automatically prefetches routes, giving fast page transitions of a strict SPA while persisting application routing state to the URL for linking and sharing; (3) allowing gradual progression from static site or strict SPA to server-side features like React Server Components and Server Actions as the project grows.

Streaming platform deployment support

Node.js server deployments support streaming. Docker container deployments support streaming. Static export deployments do not support streaming. Adapter-based deployments have platform-specific streaming support.

Streaming summary and key decisions

The trigger for streaming is your code: async work, non-deterministic output, or runtime data. When the framework encounters these, it walks up the tree looking for a Suspense boundary to use as a fallback. Everything above those boundaries forms the static shell, which is sent immediately. As each boundary resolves, React streams the result into the page. The key decisions are what to cache and where to place Suspense boundaries. Cache what you can with 'use cache' to grow the static shell. Push dynamic access down to the components that need it, and wrap those in Suspense. Everything else becomes part of the shell.

Early resource discovery with streaming

The static shell includes link and script tags in the very first HTML chunk. The browser discovers and starts fetching CSS, JavaScript, and fonts immediately while the server is still generating content. Resources are fetched during server think time rather than after it.

Streaming definition and benefits

Streaming in Next.js uses chunked transfer encoding to send parts of the HTML response as they become ready, rather than waiting for the full document. This is especially valuable for pages combining fast static content (headers, navigation, layout) with slower dynamic content (personalized data, analytics, recommendations). The static parts can be prerendered and served from a CDN instantly while dynamic parts stream in from the server as they resolve.

Static shell definition

The static shell is everything that renders before any async work resolves, including layouts, navigation, and the fallback UI defined by Suspense boundaries. It is sent immediately to the user, giving them something to see and interact with while dynamic content streams in. With Cache Components, the static shell is prerendered at build time and served instantly from the edge.

How HTML stream works

React's server renderer produces progressive HTML chunks. Static parts of the page render first and are sent immediately. When an async Server Component resolves, React streams its completed HTML along with inline script tags: one that swaps the fallback DOM node with the new content, and another carrying the component payload for hydration. The browser executes the swap instantly without waiting for the JavaScript bundle to load or hydration to complete.

Component payload definition

The component payload is a serialized representation of the component tree that React uses to hydrate the page and handle client-side updates. On initial page load, it arrives embedded in the HTML stream as inline script tags. On client-side navigation, only the component payload is fetched (with an rsc: 1 request header) and no HTML is transferred.

loading.js automatic wrapping

A loading.js file placed alongside page.js causes Next.js to automatically wrap the page content in a Suspense boundary, using the loading component as the fallback. Behind the scenes, loading.js is nested inside layout.js and wraps page.js in a Suspense boundary. The layout renders immediately as part of the static shell, the loading skeleton is shown instantly as the Suspense fallback, and when the page component finishes loading, its HTML replaces the skeleton.

Suspense boundaries stream independently

Each Suspense boundary is an independent streaming point. Components inside different boundaries resolve and stream in independently without blocking each other. Multiple components performing async work can each be wrapped in their own Suspense boundary, and each will stream as its async work completes, in whatever order that happens.

Bot and crawler metadata handling

HTML-limited bots and crawlers need metadata in the head of the initial HTML. Next.js detects them by user agent and waits for generateMetadata to resolve before streaming the page content. Full browsers and DOM-capable crawlers can receive streaming metadata alongside the page content. This behavior can be customized with the htmlLimitedBots configuration option.

Push dynamic access down pattern

The key to maximizing what streams instantly is to defer dynamic data access to the component that actually needs it. This applies to params, searchParams, cookies(), headers(), and data fetches. If any of these are awaited at the top of a layout or page, everything below that point becomes dynamic and cannot be prerendered as part of the static shell. Instead, pass the promise down and let the consuming component resolve it inside a Suspense boundary.

loading.js vs Suspense comparison

loading.js has page-wide scope, requires dropping in a file, is prefetched as instant fallback on navigation, and is best for pages where nothing renders without data. Suspense has component-level scope, requires explicit wrapping, is not prefetched by default, and is best for most pages for granular control. Prefer explicit Suspense boundaries close to the dynamic access. When the prerenderer encounters dynamic work, it walks up the tree for the nearest Suspense boundary. If none is found, the build fails with a blocking route error. A high-level loading.js serves as a valid boundary but causes the entire page to fall back to a full-page skeleton instead of streaming granularly.

Error handling mid-stream

If a component throws an error after streaming has started, the nearest error.js boundary catches it and renders the error UI in place of the failed component. The rest of the page remains intact, with only the section that errored being replaced. Because the HTTP status code (200 OK) has already been sent with the first chunk, it cannot be changed to a 4xx or 5xx error code.

Streaming data to client components example

A Server Component can start a fetch and pass the unresolved promise as a prop to a Client Component. The promise can be passed through multiple layers, with only the component calling React's use() API needing a Suspense boundary. Example: Server Component starts getStats() without awaiting and passes statsPromise to Client Component StatsChart, which uses the use() hook to read the value inside the Suspense boundary fallback.

Sharing promise across component tree

When multiple components need the same data, start the fetch once and pass the promise through a context provider so any component in the subtree can resolve it with use(). This avoids multiple fetches for the same data.

Route Handler streaming with ReadableStream

Route Handlers can stream raw responses using the Web Streams API for Server-Sent Events, large file generation, or progressive data arrival. Use the ReadableStream API with a controller to enqueue chunks. For example, create a stream that enqueues text chunks with 200ms delays, then return new Response(stream, {headers}).

Streaming file download without loading into memory

Use FileHandle.readableWebStream() to get a Web ReadableStream directly from a file, avoiding loading the entire file into memory. Example: const file = await open('/path/to/file.csv'); return new Response(file.readableWebStream(), {headers}).

TTFB and FCP improvement with streaming

Without streaming, the server waits for all data before sending any HTML, so TTFB (Time to First Byte) equals the slowest query. With streaming, the server sends the static shell as soon as it's ready, so TTFB drops to the time it takes to render layouts and fallbacks. The browser paints the static shell immediately, decoupling FCP (First Contentful Paint) from data fetching time.

LCP placement in Suspense boundaries

If the LCP (Largest Contentful Paint) element (hero image, main heading, product photo) is inside a Suspense boundary, it cannot paint until that boundary resolves. Keep LCP elements outside or above Suspense boundaries so they render as part of the static shell. Use the preload prop on next/image for LCP images to inject a link rel="preload" into the head, letting the browser start fetching from the first chunk before the img tag appears.

CLS mitigation with Suspense fallbacks

When a Suspense fallback is replaced by resolved content, the browser reflows the page. To minimize CLS (Cumulative Layout Shift), design skeleton fallbacks that match the dimensions of the content they represent. A skeleton with the same height and width as the final card grid prevents shifts. Use fixed or min-height containers around Suspense boundaries to reserve space before content arrives.

INP improvement with selective hydration

Streaming enables selective hydration where React hydrates components independently as they stream in and prioritizes hydrating whatever the user is interacting with. Each Suspense boundary is a hydration unit. Without boundaries, React hydrates the entire page in one blocking pass. With them, hydration is broken into smaller tasks that yield to the browser, keeping the main thread responsive.

HTTP status code commitment at streaming start

Once streaming begins, the HTTP response headers including the status code have already been sent to the client and cannot be changed. When a Suspense fallback renders or a component suspends, the server must commit to 200 OK to start sending the HTML stream. If notFound() fires mid-stream, Next.js cannot change the status to 404. Instead, it injects meta name="robots" content="noindex" into the streamed HTML so search engines don't index the page. A redirect() mid-stream becomes a client-side redirect rather than an HTTP redirect header.

When streaming starts

The response body begins streaming when a Suspense fallback renders (for example, loading.tsx) or when a component suspends under a Suspense boundary. To get a real HTTP status code for errors, place notFound() before any await or Suspense boundary.

Cache Components with bots and crawlers

With Cache Components, visitors and DOM-capable crawlers receive the prerendered shell immediately with dynamic content streaming in as it resolves. HTML-limited bots skip the prerendered shell and render the page dynamically so metadata can be placed in the head. Once metadata resolves, remaining page content can still stream. Keep in mind when the prerendered shell depends on inputs that only exist during prerendering (build-time data, values not reachable in request-time environment). Visitors and DOM-capable crawlers receive the shell without re-running that code, but an HTML-limited bot re-renders it dynamically, so a page loading for a person can fail to render for a crawler.

Reverse proxy buffering configuration

Nginx and similar reverse proxies buffer responses by default. Disable buffering by setting the X-Accel-Buffering header to 'no' in next.config.js using the headers() configuration.

CDN streaming support

Content Delivery Networks may buffer entire responses before forwarding them to the client. Check your CDN provider's documentation for streaming support. Some require specific configuration or plan tiers to pass through chunked responses.

Serverless platform streaming support

Not all serverless environments support streaming. AWS Lambda requires response streaming mode to be explicitly enabled (it is not the default). Vercel supports streaming natively.

Compression buffering impact on streaming

Gzip and Brotli compression can buffer chunks internally before flushing, as the compression algorithm needs enough data to compress efficiently. This can add latency to the first visible chunk. If streaming delays are noticed, check whether the compression layer is flushing aggressively enough.

Safari/WebKit streaming buffering

Safari/WebKit buffers streaming responses until 1024 bytes have been received, so very small responses paint all at once instead of progressively. Real applications easily exceed this threshold (layouts, styles, scripts), so this only affects minimal demos or tiny Route Handler responses.

Verify streaming with Chrome DevTools Network tab

To confirm HTTP response is actually arriving in chunks, select the document request in Chrome DevTools Network tab and look at the 'Timing' breakdown. A long 'Content Download' phase with an early 'Time to First Byte' confirms the response is streaming rather than arriving all at once.

Give your agent this brain