/

shadcn With React 19 and Server Components

Most catalogue components are marked client whether they need it or not. The four triggers that actually require it, and where the boundary belongs.

Serafim Korablev
Serafim Korablev
@korablev

Most component libraries are client libraries, and most catalogue components are marked "use client" whether they need it or not. In an application built on server components that is not a compatibility problem, it is a cost problem: every client component and everything it imports ships to the browser.

Here is how to tell which components genuinely need it, and what React 19 changed.

The rule

A component needs "use client" if it uses state, an effect, a ref, a browser API or an event handler. That is the whole list.

Which means a surprising number of catalogue components do not need it: a card, a badge, a table row, a static hero, a footer, a pricing block. They are marked client because the author added the directive at the top of every file, or because one child in the tree needed it and it propagated upward.

The practical exercise: open the component, look for the four things above, and if none are there, delete the directive and see whether it still builds.

Pushing the boundary down

The pattern that matters more than any individual component. A section that is 95% static markup with one interactive control does not need to be a client component; the control does.

Split it: keep the section as a server component and extract the interactive part into its own client component. That way the markup, the copy and everything they import stay on the server, and only the small piece ships.

The reverse mistake is the common one: a "use client" at the top of a page, which makes the entire tree beneath it client-side, including components that never needed to be.

Passing things across the boundary

Two rules, both of which produce confusing errors when broken.

Props must be serialisable. A server component can pass strings, numbers, plain objects and arrays to a client component. It cannot pass a function, a class instance or a Date in the way people expect. Functions are the common trip: an onSelect handler defined in a server component cannot be handed to a client child.

Children can cross. A client component can render server-rendered children passed as children, which is what makes a client-side layout wrapper around server content possible. This is the escape hatch for a provider or an animation wrapper: make the wrapper the client component and let its children stay on the server.

What React 19 changed

Three things worth knowing for component work.

ref is a normal prop. Function components take ref directly, so the forwardRef wrapper that every primitive used is no longer required. Existing code still works; new components can drop the ceremony.

Form actions and the pending state. A form can take an action directly, and the hooks that report submission state replace the manual boolean. The forms guide covers what that changes in practice, including the fact that a real form works before hydration.

Metadata and stylesheets hoist. Rendering a title or a link tag inside a component now does what you expect rather than needing a separate mechanism.

Checking what you actually ship

Two habits catch almost everything.

Search your components for "use client" and ask, file by file, which of the four triggers applies. In most codebases a third of the directives are inherited rather than needed.

Then look at the bundle. A client component that imports a date library, an icon set or a chart package pulls all of it to the browser, and that is where the surprises are - not in the component's own code, but in what it imported.

Where else to look

The honest list, because the answer is not always us:

SourceBest forTrade-off
React docs on server componentsThe canonical boundary rulesFramework-agnostic, so light on practice
Your framework's docsHow the boundary actually works in your routerVersion-specific
shadcn/uiPrimitives that mark client only where neededYou still place the boundary
21stComponents with their source visible before you installMany are marked client by default; check before assuming

Taking one

Every component page has a live preview and the code. Installing goes through the shadcn CLI against our registry:

bash

That key comes from your 21st account, and installs require a membership. Set API_KEY_21ST once in your shell and the command works for anything in the catalogue.

Browse components →

Frequently asked

When does a component need "use client"?
When it uses state, an effect, a ref, a browser API or an event handler. That is the whole list, and it means a card, a badge, a static hero and a footer usually do not, even though many catalogue components are marked client because the author added the directive to every file.
How do you keep a mostly static section on the server?
Push the boundary down. Keep the section as a server component and extract only the interactive control into its own client component. The reverse, a use client at the top of the page, drags the entire tree and everything it imports into the browser bundle.
What can a server component pass to a client component?
Serialisable values: strings, numbers, plain objects and arrays. Not functions, class instances or anything expecting to survive as a live reference. Children are the exception and the escape hatch: a client wrapper can render server-rendered children passed to it.
What did React 19 change for component libraries?
ref is a normal prop, so the forwardRef ceremony is no longer required. Forms take an action directly, with hooks reporting the pending state instead of a manual boolean. And metadata and stylesheets rendered inside a component hoist as expected.

Published

Aug 20, 2026

Read time

4 min

Tags

How-toReactPerformanceshadcn

Share