Skip to content
HomePagesHomePages template kit

Server and client

Why a renderer ships no JavaScript and interactivity is relocated into a separate, explicitly marked file.

A section’s renderer is server-rendered: it produces markup and ships no JavaScript of its own. Anything that needs a browser — state, an event handler, a piece of a third-party client library — is not written inside the renderer at all. It is written as a separate, ordinary React component marked "use client" and mounted from the renderer as an island. See Islands for how to write one.

The pipeline establishes that your code runs at exactly two moments: once when a deliverable is published, and repeatedly, behind the editor, while a user edits. Neither of those moments happens in a browser — the editor injects server-produced HTML into an iframe rather than loading a renderer bundle of its own. Three consequences follow from that, in order:

  1. A published page is rendered once and served as static files. A renderer that reached for a clock, a random number, or a browser global could not be rendered at publish time at all — there is no browser present to answer it, and no request in flight to answer it differently next time.
  2. The editor re-renders your component repeatedly while a user edits, against content that keeps changing under it. A renderer with side effects would misbehave every time that happened, not just once — the same problem the first consequence names, recurring on a shorter clock.
  3. An island’s props are embedded in the HTML the server produced, then read back out of that markup in the browser. They cross the server-to-client boundary as data, not as live references — which is why they must be JSON-serializable (see serializable-island-props), and why there is exactly one React instance on the page: the renderer’s output and the island’s hydration target are the same markup, not two trees reconciled against each other.

The "use client" directive is per file, not per component tree, so the boundary between the two worlds is a file boundary you draw yourself. Everything in a file without the directive runs only where the page is built — the platform’s server at publish, and again behind the editor when it re-renders your section. Everything in a file marked "use client" also runs in the browser, hydrated in place once the server-rendered markup carrying it has loaded. Splitting a section along that boundary is what lets the renderer stay a pure function of its props: state, effects, and event handlers all move to the island side of the file boundary and never appear on the renderer side.

server-vs-client is the rule that draws this line in practice — what the directive scopes, and which other rules stop at it. no-client-runtime-in-server is the one that enforces it directly: no useEffect, no fetch, no other browser-only hook or global, and no event-handler prop, in a file on the renderer side of that boundary.

Not React Server Components as a Next.js reader knows them. There is no per-request rendering here — a deliverable renders once, at publish, not on each visitor’s request — and no data fetching happens inside a renderer. A section’s content arrives already decided, on its props, from the fill stage described in The pipeline.

Not a restriction on what a section may do. Interactivity is relocated, not banned — a filter, a slider, a lightbox are all ordinary things to build, just in their own file, on the other side of the line the renderer draws.