Web Development

React 19 in Production: Server Components, Actions, and the New Mental Model

React 19 didn't just ship new APIs — it shipped a new default architecture. Here's the mental model we use to decide what runs on the server, what runs on the client, and why most components should have no "use client" at all.

KAKabir AnandLead Developer
12 min read
Eyeglasses reflecting a blurred dual-monitor setup showing colorful code

Every React upgrade used to be additive — new hooks, new APIs, same mental model. React 19 is different. Server Components, Actions and the compiler together change the default answer to "where does this code run," and teams that keep their pre-19 instincts end up shipping more client JavaScript than they need to.

We've migrated six production apps onto React 19 this year. The wins were real, but only after we rebuilt the mental model — not the code — first.

The default flipped: server-first, not client-first

Before React 19, every component was implicitly a client component — you added a boundary to opt into server rendering. Now the default is the opposite: a component is a Server Component unless it explicitly says otherwise. That single flip changes how you should structure a new feature from the first line.

Where a component's code actually runs

New componentNo directive — server by default
Needs state, effects, or events?Add "use client" at the leaf
Client boundaryShips JS to the browser
Everything above itStays server-rendered, zero JS
  • Push “use client” as deep as possible — the boundary should sit on the smallest interactive leaf (a button, a form), not on the page or layout that wraps it.
  • Data fetching moves back into components— no more prop-drilling a fetch result through three layers just to avoid a waterfall; a Server Component can fetch exactly where it’s used.
  • Client components can’t import server-only code — the build fails loudly instead of silently leaking a database credential into the browser bundle, which was previously a runtime-only mistake.

Actions replace half your API routes

Server Actions let a form submit directly to a server function — no separate API route, no manual fetch, no client-side state machine for loading/error/success. For CRUD-shaped mutations (the majority of what a typical SaaS ships), this removes an entire category of boilerplate.

app/actions/createProject.ts
1"use server";
2 
3import { db } from "@/lib/db";
4import { revalidatePath } from "next/cache";
5 
6export async function createProject(formData: FormData) {
7  const name = formData.get("name") as string;
8  await db.project.create({ data: { name } });
9  revalidatePath("/projects");
10}
Where we still keep a real API route

Actions are for form-shaped mutations triggered from React. Anything a third party calls directly — webhooks, a mobile client, a public API — still needs a real route handler. Don't force those through an Action just for consistency.

The compiler removes most useMemo/useCallback

The React Compiler (stable as of 19) auto-memoizes components and values based on static analysis of your code, not manual dependency arrays. In our migrations, roughly 90% of hand-written useMemo/useCallback calls became redundant — the compiler already does that work, correctly, without a dependency array to get wrong.

  • 1Remove memoization hooks after confirming the compiler is enabled — don't remove them speculatively first, or you'll reintroduce real re-render bugs on older React versions mid-migration.
  • 2Keep useMemo only for genuinely expensive, non-rendering computation (a large sort, a heavy parse) — the compiler optimizes render behavior, not arbitrary CPU work.
  • 3Run the compiler's ESLint plugin in CI before flipping it on in production — it flags the handful of patterns (mutating props, conditional hooks) that break its assumptions.

38%

average client bundle size reduction after moving to Server Components

90%

of hand-written useMemo/useCallback calls the compiler made redundant

6

production apps migrated this year with zero rollback

We stopped asking “should this be a client component” and started asking “does this specific element need to react to a click.” Everything else answers itself.

Kabir Anand, Lead Developer

Key Takeaways

  • React 19's real default is server-first — write plain components, and only add "use client" on the smallest interactive leaf.
  • Server Actions replace most CRUD-shaped API routes; keep real route handlers only for endpoints called from outside React.
  • The compiler handles most memoization automatically — audit and remove hand-written useMemo/useCallback after confirming it's enabled, not before.
  • A build-time error when client code imports server-only modules catches secret leaks that used to be silent runtime bugs.

Where to start

Don't attempt a big-bang rewrite. Pick one new feature, build it server-first with the smallest possible client boundary, and use it to recalibrate the team's instincts before touching anything that already works.

Keep Reading

More from the blog

The Engineering Partner You Can Build On

Reliable software takes an experienced team that owns delivery end to end. Here’s the track record behind ours.

11+

Years Building Custom Software

320+

Projects Delivered Across Web, Mobile & AI

85%

Repeat Client Rate

12+

Countries Served Worldwide

Trusted by startups and enterprises worldwide

Work With Us

Let’s create
with purpose

Share your goals, timeline, and challenges — we’ll respond with clarity and next steps.

Ambitious ideas deserve thoughtful execution. Start the conversation and let’s define what success looks like.

Team

Acetrum

Est. 2015

4.9/5

Trusted by
top brands

Services interested in:

By submitting, I confirm I’ve read and agree with Privacy and Cookie Policies.

Newsletter

Signals worth
paying attention

No recycled headlines — just the patterns we’re seeing across real client work, distilled into one read a month.

A curated digest of practical thinking and real-world brand perspectives monthly.

No spam. Unsubscribe anytime.