DEV Community

Han
Han

Posted on

Render Response: The Case for Pure React Server Components

You can call cookies() in a React Server Component today.
But if you try to set a cookie, nothing happens.

This behavior is intentional.

React Server Components (RSC) do not participate in the HTTP response lifecycle. They execute during rendering — a phase that can be repeated, restarted, speculated, aborted, cached, or occur without a corresponding client request. This is a fundamental constraint of the RSC model, not a missing feature.

Rendering produces descriptions of UI.
Responses produce effects on the outside world.

These concerns are separated for correctness.

Renders May Replay

A render is not guaranteed to correspond to a user event or to a single network response. Frameworks are free to:

  • Replay renders after suspending
  • Retry renders after errors
  • Run renders for background revalidation
  • Perform partial renders during streaming
  • Cancel renders before they reach the client
  • Execute renders in different environments (edge, regional worker, etc.)

From the perspective of React, a render is a pure computation.
From the perspective of HTTP, the response is a side-effect.

If a render were allowed to mutate the response, any of the behaviors above could trigger multiple or mis-timed effects. The result would be non-deterministic.

We would no longer be able to rely on:

  • Caching
  • Correct concurrency
  • Streaming behavior
  • Stable error recovery

In short: purity enables the RSC model.

Snapshots, Not Mutations

cookies() and headers() exist in RSCs only as read-only snapshots of the incoming request. They are asynchronous because the framework may suspend to produce a consistent view during streaming.

Example:

import { cookies, headers } from 'next/headers';

export default async function Page() {
  const c = await cookies();
  const h = await headers();

  const theme = c.get('theme')?.value ?? 'light';
  const ua = h.get('user-agent') ?? 'n/a';

  return <main data-theme={theme}>{ua}</main>;
}
Enter fullscreen mode Exit fullscreen mode

These reads are pure. They do not cause effects.
They do not depend on timing.
They can be replayed safely.

Writes cannot satisfy these properties, so they are prohibited.

Purity as a Design Requirement

The idempotence requirement can be stated simply:

“The result of rendering must not depend on whether rendering has been performed once or many times.”

More formally:

Let R be a render function producing output U from state S.
The function R must satisfy:

R(S) = U
R(R(S)) = U

If a render performs side effects — writing to a database, emitting network logs, modifying headers or cookies — this property is violated. Any concurrency model depending on retries or cancellation would break.

Purity is not a recommendation.
It is the only model that keeps the semantics sound.

captionless image

Where Effects Belong

When mutations do need to occur, they must be tied to a specific HTTP response or a user-triggered transition, where exactly-once semantics are controlled.

Two places provide those guarantees:

Route Handlers
Full response construction: status, headers, cookies.

Server Actions
User-driven mutations; the framework binds effects to the resulting response.

Everything else — including Server Components — must remain pure.

The Model in One Sentence

Rendering is a computation.
Response is an effect.
Only one of them can safely replay.

Conclusion

React Server Components expand what is possible for server-driven UI.
But they do so by imposing a strict invariant:

“Rendering must be pure.”

If you need to modify the outside world, do not do it in an RSC.
Render the UI.
Perform effects in handlers or actions.
Let the system retry safely.

You can think more than once.
You can only act once.

Top comments (0)