← Back to articles

next-safe-action vs tRPC vs Server Actions: Best Data Layer for Next.js (2026)

Next.js Server Actions let you call server code from the client. But raw Server Actions lack validation, error handling, and middleware. next-safe-action and tRPC add what's missing. Here's how to choose.

Quick Comparison

Featurenext-safe-actiontRPCPlain Server Actions
TypeServer Action wrapperFull RPC frameworkBuilt into Next.js
ValidationZod (built-in)Zod (built-in)Manual
Type safetyFull (input → output)Full (end-to-end)Basic
MiddlewareYesYes (powerful)No
Error handlingStructuredStructuredManual try/catch
Auth contextVia middlewareVia context/middlewareManual
Optimistic updatesuseOptimisticAction hookVia React QueryManual
React Query integrationOptionalBuilt-inNo
Bundle size~5KB~15KB (client)0KB
SetupMinimalModerateZero

Plain Server Actions: The Baseline

Server Actions are built into Next.js. No library needed.

// app/actions.ts
"use server"

export async function createTask(formData: FormData) {
  const title = formData.get("title") as string
  // No validation, no error handling, no auth check
  await db.task.create({ data: { title } })
  revalidatePath("/tasks")
}

Strengths

  • Zero dependencies, zero setup
  • Built into Next.js — always up to date
  • Progressive enhancement (works without JS)
  • Simple mental model

Weaknesses

  • No input validation (you build it yourself)
  • No structured error handling
  • No middleware (auth checks repeated in every action)
  • No type-safe return values
  • No optimistic update helpers
  • Easy to write insecure code

Best For

Prototypes, simple forms, and apps where you'll add validation manually anyway.

next-safe-action: Server Actions Done Right

next-safe-action wraps Server Actions with Zod validation, middleware, and structured error handling. It's the minimal upgrade from raw Server Actions.

// lib/safe-action.ts
import { createSafeActionClient } from "next-safe-action"

export const action = createSafeActionClient({
  handleServerError(e) {
    return "Something went wrong"
  },
})

export const authAction = action.use(async ({ next }) => {
  const session = await getSession()
  if (!session) throw new Error("Unauthorized")
  return next({ ctx: { userId: session.userId } })
})
// app/actions.ts
"use server"
import { authAction } from "@/lib/safe-action"
import { z } from "zod"

export const createTask = authAction
  .schema(z.object({ title: z.string().min(1).max(100) }))
  .action(async ({ parsedInput, ctx }) => {
    const task = await db.task.create({
      data: { title: parsedInput.title, userId: ctx.userId },
    })
    return { task }
  })
// components/CreateTaskForm.tsx
"use client"
import { useAction } from "next-safe-action/hooks"
import { createTask } from "@/app/actions"

function CreateTaskForm() {
  const { execute, result, isExecuting } = useAction(createTask)
  
  return (
    <form onSubmit={(e) => {
      e.preventDefault()
      execute({ title: e.currentTarget.title.value })
    }}>
      {result.validationErrors?.title && <p>{result.validationErrors.title}</p>}
      {result.serverError && <p>{result.serverError}</p>}
      <input name="title" />
      <button disabled={isExecuting}>Create</button>
    </form>
  )
}

Strengths

  • Minimal overhead. Small library that wraps what you already use (Server Actions).
  • Zod validation. Input validation with type inference. Validation errors returned to the client automatically.
  • Middleware chain. Auth, logging, rate limiting — compose middleware like Express.
  • Structured errors. Separate validation errors from server errors. Client knows exactly what went wrong.
  • Optimistic updates. useOptimisticAction hook for instant UI feedback.
  • Stays close to Next.js. Uses Server Actions under the hood. No separate API layer.
  • ~5KB. Tiny bundle impact.

Weaknesses

  • Next.js only. Not portable to other frameworks.
  • Less powerful than tRPC. No subscriptions, no React Query integration by default, simpler middleware.
  • Newer library. Smaller community than tRPC.
  • No batch requests. Each action is a separate request.

Best For

Next.js App Router projects that want validated, type-safe Server Actions with minimal setup. The "just enough" solution.

tRPC: The Full RPC Framework

tRPC is a full end-to-end type-safe RPC framework. It predates Server Actions and provides a more comprehensive data layer.

// server/trpc.ts
import { initTRPC } from "@trpc/server"

const t = initTRPC.context<Context>().create()

export const router = t.router
export const publicProcedure = t.procedure
export const protectedProcedure = t.procedure.use(authMiddleware)
// server/routers/tasks.ts
export const taskRouter = router({
  list: protectedProcedure
    .query(async ({ ctx }) => {
      return db.task.findMany({ where: { userId: ctx.userId } })
    }),
  
  create: protectedProcedure
    .input(z.object({ title: z.string().min(1) }))
    .mutation(async ({ input, ctx }) => {
      return db.task.create({
        data: { title: input.title, userId: ctx.userId },
      })
    }),
})
// components/Tasks.tsx
"use client"
import { trpc } from "@/lib/trpc"

function Tasks() {
  const { data: tasks } = trpc.task.list.useQuery()
  const createTask = trpc.task.create.useMutation({
    onSuccess: () => utils.task.list.invalidate(),
  })

  return (/* ... */)
}

Strengths

  • Most mature. Years of production use, large community, extensive ecosystem.
  • React Query integration. Built on TanStack Query — caching, deduplication, background refetching, pagination.
  • Powerful middleware. Composable, chainable, with full TypeScript inference.
  • Router structure. Organize procedures into logical routers (tasks, users, billing).
  • Subscriptions. WebSocket support for real-time features.
  • Framework-agnostic. Works with Next.js, Remix, Express, Fastify, etc.
  • Batch requests. Multiple queries in a single HTTP request.

Weaknesses

  • More setup. Router, context, provider, client configuration — more boilerplate than next-safe-action.
  • Separate API layer. Not using Server Actions — creates its own API endpoints. Two mental models to manage.
  • Larger bundle. ~15KB client-side (React Query + tRPC client).
  • Learning curve. Router patterns, context, middleware — more concepts to learn.
  • Doesn't use Server Actions. Misses progressive enhancement and streaming benefits.

Best For

Apps with complex data requirements, real-time features, or that need to work across multiple frameworks. Teams coming from REST/GraphQL who want type-safe RPC.

Head-to-Head Scenarios

Simple Form Submission

Winner: next-safe-action. Wraps Server Actions with validation and error handling. Minimal code.

Complex Dashboard with Multiple Data Sources

Winner: tRPC. React Query's caching, background refetching, and batch requests shine for data-heavy UIs.

Real-Time Features (Chat, Notifications)

Winner: tRPC. Subscription support via WebSocket. next-safe-action and Server Actions have no built-in real-time.

New Next.js App Router Project

Winner: next-safe-action. Stays native to Next.js. Uses Server Actions, which integrate with React's latest features (progressive enhancement, streaming).

Multi-Framework or Non-Next.js Project

Winner: tRPC. Framework-agnostic. Works with any backend.

Can You Use Both?

Yes. A pragmatic approach:

  • next-safe-action for mutations (form submissions, writes)
  • tRPC for queries (data fetching with caching)

This gives you the best of both worlds but adds complexity. Only recommended for larger apps.

Migration Paths

From Plain Server Actions → next-safe-action

Easy. Wrap existing actions with action.schema().action(). Incremental migration possible.

From tRPC → next-safe-action

Moderate. Rewrite procedures as Server Actions. Lose React Query caching (add TanStack Query separately if needed).

From next-safe-action → tRPC

Moderate. Create router structure, set up provider. Existing action logic transfers cleanly.

FAQ

Do I need either of these? Can't I just validate manually?

You can, but you'll rewrite the same validation/error/auth boilerplate in every Server Action. These libraries eliminate that repetition.

Does next-safe-action work with the Pages Router?

No. It requires Server Actions, which are App Router only. Use tRPC for Pages Router.

What about Zsa?

Zsa is another Server Action library similar to next-safe-action. Both are good. next-safe-action has a larger community and more features as of 2026.

Is tRPC still relevant with Server Actions?

Yes. tRPC provides features Server Actions don't: caching (React Query), batching, subscriptions, and framework portability. If you need any of these, tRPC is still the right choice.

The Verdict

  • Plain Server Actions for prototypes and very simple apps. Add validation later.
  • next-safe-action for most Next.js App Router projects. The right amount of structure with minimal overhead.
  • tRPC for complex data requirements, real-time features, or multi-framework codebases.

For most Next.js projects starting in 2026, next-safe-action is the sweet spot. It solves the real problems with raw Server Actions (validation, errors, auth) without the complexity of a full RPC framework.

Get AI tool guides in your inbox

Weekly deep-dives on the best AI coding tools, automation platforms, and productivity software.