Claude Fable 5.1 & GPT-6 Astra packages are live

Client Components

Free

Client components — when interactivity justifies the bundle, hydration correctness, browser-API access, and keeping the boundary small.

194 lines7.6 KB Mistral Frontend
targetModels
Mistral Medium 3.5Mistral Large 3Mistral Small 4Mistral FamilyFuture Mistral Models
name
client-components
category
Frontend
description
Client components — when interactivity justifies the bundle, hydration correctness, browser-API access, and keeping the boundary small.
license
MIT
author
Agent.md maintainers
last-verified
reviewed-by
unreviewed
<!-- Generated from models/_canonical by scripts/build-model-variants.js. Edit the canonical source, not this file. Behavioural profile for Mistral: scripts/model-profiles.json -->

#How to apply this file

Each section opens with one imperative line; apply every rule in the section it introduces. Do not summarise or skip a section.


#Purpose

Rules for "use client" components. Every one of them costs download, parse and execution time on the user's device, so each should exist for a reason that can be named: state, an event handler, a browser API, or an effect.

The server side is Frontend/server-components; hydration specifics are Frontend/hydration.


#"use client" is an entry point, not a file marker

[INST] Apply every rule in this section: "use client" is an entry point, not a file marker. [/INST]

tsx
"use client";
import { Chart } from "heavy-charting-lib";     // 180 KB — now in the bundle
import { formatMoney } from "@/lib/format";     // and this, and its imports

The directive marks the start of the client boundary. Everything reachable from it — transitively — is bundled and shipped.

Consequences worth remembering:

  • One "use client" at the top of a layout ships that entire subtree.
  • A utility imported by both server and client code ends up in the bundle.
  • Marking a file that needs no interactivity costs bytes for nothing.

Keep the boundary at the leaf:

tsx
// The page stays on the server; only the button is a client component
export default async function Page() {
  const product = await getProduct(id);
  return <article><ProductDetails product={product} /><AddToCart id={product.id} /></article>;
}

#Justify each one

[INST] Apply every rule in this section: Justify each one. [/INST]

A client component needs at least one of:

ReasonExample
State that changes from interactionA dropdown, a form field
An event handleronClick, onChange, onSubmit
A browser APIlocalStorage, matchMedia, IntersectionObserver
An effect synchronising with something externalAn analytics SDK, a map library
A third-party library that uses any of the aboveMost UI kits

If none applies, it is a server component.

Split rather than escalating: a card whose only interactive element is a "copy" button should be a server component containing a small client <CopyButton>, not a client component containing a card.


#Hydration must match

[INST] Apply every rule in this section: Hydration must match. [/INST]

React renders on the server and then attaches on the client. If the two produce different HTML, React discards the server markup and re-renders — losing the performance benefit and, in some cases, producing visibly wrong content.

tsx
// Mismatch: the server and client evaluate these at different moments
<span>{new Date().toLocaleTimeString()}</span>
<span>{Math.random()}</span>
<span>{window.innerWidth}</span>        // window does not exist on the server

Correct patterns:

tsx
// Values that genuinely differ: render after mount
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
if (!mounted) return <Placeholder />;

// Reading an external store: the server snapshot must be deterministic
const theme = useSyncExternalStore(subscribe, getClientSnapshot, () => "light");

Never read window, document, localStorage or navigator during render. They are undefined on the server. Read them in an effect or through useSyncExternalStore.

Use useId() for generated ids that must be stable across the boundary — a random id will differ between server and client. → Frontend/hydration


#Props and data

[INST] Apply every rule in this section: Props and data. [/INST]

  • Props from a server component are serialised into the HTML. Pass the minimum, and never pass anything the user should not see.
  • Functions cannot cross the boundary, except Server Action references.
  • Server data belongs in a server component or a query cache, not fetched again on mount. Refetching on the client duplicates work and creates a waterfall. → Frontend/state-management
  • A client component receiving children from a server component does not bundle those children — that is the composition escape hatch for heavy content.

#Keep the bundle honest

[INST] Apply every rule in this section: Keep the bundle honest. [/INST]

  • Lazy-load heavy interactive components so they are not in the initial payload:
tsx
const Editor = dynamic(() => import("./Editor"), { ssr: false, loading: () => <Skeleton /> });
  • ssr: false for anything that genuinely cannot render on the server (a map, a canvas visualisation) — but it also means nothing renders until the JavaScript arrives, so reserve it.
  • Check what a "use client" file actually pulls in with a bundle analyser. A single icon import can drag in an entire library.
  • Prefer platform APIs over dependencies inside client components — every byte is paid by the user, on their device. → Frontend/performance

#Anti-patterns

[INST] Apply every rule in this section: Anti-patterns. [/INST]

Anti-patternWhy it failsFix
"use client" on a layout or pageThe whole subtree ships to the browserMark interactive leaves
Marking a file that needs no interactivityBytes for nothingLeave it on the server
A client component wrapping static contentShips content that could be HTMLInvert: server wraps client
Reading window during renderUndefined on the server; hydration errorEffect or useSyncExternalStore
Date/Math.random in renderServer and client differ; mismatchRender after mount
Random or index-derived idsDiffer across the boundaryuseId()
Suppressing hydration warningsHides a real mismatchFix the cause
Refetching server data on mountDuplicate work; waterfallPass it down or use a cache
Passing whole records as propsSerialised into the HTMLProject explicit fields
Heavy component in the initial bundleSlow time-to-interactivedynamic import
ssr: false by defaultNothing renders until JS loadsOnly where genuinely required
Not checking what a client file importsOne icon drags in a libraryBundle analysis

#Checklist

  • Verify: Every "use client" file has a named reason: state, handler, browser API or effect
  • Verify: The directive sits at interactive leaves, never at layouts or pages
  • Verify: Static content is not wrapped inside client components
  • Verify: No browser global is read during render
  • Verify: Time, randomness and environment-dependent values render after mount
  • Verify: Generated ids use useId()
  • Verify: No hydration warning is suppressed without fixing the cause
  • Verify: Server data is passed down or cached, not refetched on mount
  • Verify: Props crossing the boundary are minimal and explicitly projected
  • Verify: Heavy interactive components are dynamically imported
  • Verify: ssr: false is used only where server rendering is genuinely impossible
  • Verify: The client bundle has been analysed for unexpected dependencies