Server Actions vs tRPC — Stale UI After Mutation
Server Actions don't auto-revalidate.
- Server Actions are Next.js-native RPC functions ('use server') that compile to internal POST endpoints
- tRPC v11 provides end-to-end type safety with automatic TypeScript inference from router definitions
- Server Actions excel for simple mutations (forms, toggles) — zero boilerplate, native React 19 integration via useActionState and useFormStatus
- tRPC excels for complex queries — batching via httpBatchLink, caching via TanStack Query, subscriptions, middleware chains
- Server Actions serialize via structured clone (Date, Map, Set, BigInt, File work since Next.js 14.2) — not JSON-only
- Both require explicit cache invalidation: Server Actions with revalidateTag/revalidatePath (fetch must use next tags), tRPC with utils.invalidate()
- Use Server Actions for <20 mutations; choose tRPC when you need query caching, batching, or >50 typed endpoints
Imagine ordering food. Server Actions are like texting your order directly to the kitchen — fast, simple, no intermediary. tRPC is like a full restaurant ordering system with a menu (typed routes), a waiter (middleware), a kitchen display (error handling), and the ability to track multiple orders at once (batching). For a quick coffee, texting works fine. For a 20-course tasting menu, you need the system.
Next.js 16 Server Actions and tRPC v11 solve the same problem — moving data between client and server without manual HTTP endpoints — but they make different trade-offs. Server Actions prioritize simplicity: write a function, mark it 'use server', call it from a component with React 19's useActionState. tRPC prioritizes developer experience: define a router, get end-to-end types, automatic batching, and full TanStack Query integration.
The wrong choice doesn't crash — it causes architectural decay. Teams using Server Actions for 50-endpoint features end up with scattered logic and no caching. Teams using tRPC for a simple contact form spend more time on setup than building.
This 2026 guide breaks down exactly when each wins, and the hybrid pattern production teams use: Server Actions for writes, tRPC for reads.
How Server Actions Work Under the Hood
A Server Action is a function marked 'use server' that Next.js compiles into a server-only module. When called from the client, Next.js creates an encrypted POST to an internal endpoint, deserializes arguments via structured clone, executes the function, and returns the result.
Structured clone (Next.js 14.2+) supports Date, Map, Set, BigInt, RegExp, ArrayBuffer, File, and Blob. Functions, class instances with prototypes, and Symbols still fail. You no longer need to stringify Dates manually.
React 19 adds useActionState for form state and useOptimistic for optimistic UI — Server Actions can now do optimistic updates without manual state management.
- Write function, mark 'use server', call from client — zero boilerplate
- Structured clone serialization — Date/Map/Set/BigInt work; functions/class instances don't
- No middleware — add auth/logging inline or via wrapper
- Cache invalidation manual — revalidateTag AND tag your fetches
- React 19: useActionState for state, useFormStatus for pending, useOptimistic for optimistic UI
How tRPC Works Under the Hood
tRPC v11 defines a router of procedures on the server. The client imports the router type via createTRPCReact from @trpc/react-query, and TypeScript infers input/output types automatically. No manual types.
The client uses links. httpBatchLink batches calls made in the same event loop tick into one HTTP POST. Zod validation runs only on the server — not twice. Add superjson transformer to support Date/Map/Set.
tRPC integrates with TanStack Query v5. Queries are cached, deduplicated, and stale-while-revalidated. Mutations invalidate caches via utils.invalidate().
The Hybrid Architecture: Server Actions for Writes, tRPC for Reads
Production default in 2026: Server Actions for mutations, tRPC for queries. Server Actions give React 19 form integration with useActionState. tRPC gives TanStack Query caching and batching.
Both can do optimistic updates: tRPC via onMutate, Server Actions via useOptimistic. Share zod schemas to prevent drift.
- tRPC handles all reads: cached, batched, background refetch
- Server Actions handle simple writes: forms with useActionState
- Both can do optimistic UI: tRPC via onMutate, Actions via useOptimistic
- Share zod schemas in lib/schemas
Performance Comparison: Real Numbers
Single mutation: Server Actions ~45ms, tRPC ~50ms — ~5ms difference from link overhead, not double validation. 8 queries: without batching 496ms, with httpBatchLink 68ms — 7.3x faster. Cached tRPC query: 0ms vs Server Action always 120ms.
Server Actions with no revalidation caused stale UI for 6 hours during a product launch
- Server Actions do NOT auto-revalidate — call revalidateTag or revalidatePath
- revalidateTag only works if fetch is tagged — add next: { tags: [...] } to data fetches
- Test revalidation by checking UI after mutation, not just DB
- During launches, monitor DB vs cache divergence
utils.inventory.invalidate() — TanStack Query serves cached response until invalidated.Key takeaways
Common mistakes to avoid
4 patternsUsing Server Actions for read-heavy dashboards
Forgetting revalidateTag AND fetch tags
Using tRPC for simple 2-form feature
Not sharing zod schemas
Interview Questions on This Topic
When choose Server Actions vs tRPC?
Frequently Asked Questions
That's React.js. Mark it forged?
3 min read · try the examples if you haven't