Back to Deep Dives
RSCStreamingHydration

React 19 + Next.js 16 Deep Dive: Rendering, Caching, and Hydration Explained

03/202616 min read
Share

Understanding the New Rendering Model

Traditionally, React applications relied heavily on client-side rendering (CSR) or server-side rendering (SSR). With React 19 and Next.js 16, we now have a hybrid model:

  • Server Components (RSC) → Rendered on the server
  • Client Components → Hydrated and interactive in the browser
  • Streaming UI → Sent progressively to the client

This model allows you to reduce JavaScript bundle size while improving performance.

Server vs Client Components

By default, components in Next.js 16 are Server Components.

app/page.tsx
export default async function Page() { const data = await fetchData(); return <div>{data.title}</div>; }

To make a component interactive:

'use client';

The key idea is simple: render as much as possible on the server, and hydrate only what's necessary.

#1 Rendering Deep Dive: What Actually Happens

When a request hits your Next.js app:

  • The server renders Server Components
  • React generates a special payload (RSC payload)
  • HTML is streamed to the browser
  • Client Components are hydrated progressively

This is called progressive rendering with streaming.

Why Streaming Matters

Without streaming: The user waits for the full page to load.

With streaming: The user sees content immediately while the rest loads.

Example using Suspense:

app/products/page.tsx
import { Suspense } from "react"; export default function Page() { return ( <Suspense fallback={<Loading />}> <ProductList /> </Suspense> ); }

This allows parts of your UI to load independently.

Result
  • Faster perceived performance
  • Better Core Web Vitals (especially LCP)

#2 Caching in Next.js 16: Smarter Than You Think

Caching is one of the most misunderstood parts of Next.js. In Next.js 16, fetch is automatically cached by default in Server Components.

Default Behavior

// This request is cached and deduplicated automatically await fetch('/api/data');

Controlling Cache Behavior

You can explicitly define caching strategies:

Static Data (Cache Forever)

await fetch('/api/data', { cache: 'force-cache', });

Dynamic Data (No Cache)

await fetch('/api/data', { cache: 'no-store', });

Revalidation (ISR-style)

await fetch('/api/data', { next: { revalidate: 60 }, });

This means: data is cached and re-fetched every 60 seconds.

Common Caching Mistakes

  • Overusing no-store → kills performance
  • Forgetting revalidation → stale data
  • Mixing client fetching unnecessarily

Rule of thumb: fetch on the server first, cache by default, and opt out only when needed.

Result
  • Reduced server load
  • Faster responses
  • Better scalability

#3 Hydration Explained (And Why It Breaks)

Hydration is the process where React attaches event listeners to server-rendered HTML. In React 19, hydration is more efficient—but still fragile if misused.

How Hydration Works

  • Server sends HTML
  • Browser renders static content
  • React attaches interactivity (hydration)

Problems happen when: Server HTML ≠ Client HTML

Common Hydration Issues

1. Non-Deterministic Values

<p>{Math.random()}</p>

This will break hydration because the value differs between server and client.

2. Using Browser APIs on the Server

window.innerWidth

This crashes because window doesn't exist on the server.

Best Practices for Hydration

  • Keep Server Components pure
  • Move dynamic logic to Client Components
  • Avoid time-based or random values in server render

Example fix:

components/Time.tsx
'use client'; import { useEffect, useState } from 'react'; export default function Time() { const [time, setTime] = useState(''); useEffect(() => { setTime(new Date().toLocaleTimeString()); }, []); return <p>{time}</p>; }
Result
  • Stable hydration
  • Fewer runtime errors

Key Takeaways

  • 1Use Server Components by default — fetch data on the server, reduce client-side JavaScript
  • 2Leverage Streaming — break UI into smaller chunks, use Suspense boundaries
  • 3Cache aggressively — use default caching, add revalidation where needed
  • 4Be careful with hydration — ensure consistent output, isolate client-only logic

Final Thoughts

React 19 and Next.js 16 introduce a fundamentally new way of thinking about frontend architecture.

Instead of asking "Should this be SSR or CSR?" — you now ask: "What should run on the server vs the client?"

Understanding rendering, caching, and hydration is the key to unlocking better performance, lower infrastructure costs, and cleaner, more scalable codebases.

TL;DR
  • Rendering: Server-first with streaming
  • Caching: Built-in and powerful—use it wisely
  • Hydration: Fast but sensitive—keep it deterministic