{"post":{"slug":"nextjs-15-server-actions-guide-2026","title":"Next.js 15 Server Actions in 2026: patterns, auth, and when not to use them","excerpt":"Server Actions stabilized in Next.js 15. Here are the patterns that actually work in 2026 — auth flows, error handling, optimistic UI, and the cases to skip.","category":"code","date":"February 2, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">S</span>erver Actions in Next.js have been promising since 13. They stabilized properly in Next.js 15 (late 2024) and matured into a default-pattern in 2026. The reduction in boilerplate is real — most form-driven flows that previously needed an API route + fetch + state-management stack now collapse to a single annotated function. Here's the patterns that work.\n\n## What changed in 2026\n\n- **Stable since Next.js 15.1** — no more breaking changes between minors.\n- **`useActionState` (formerly `useFormState`) replaced the older patterns.** Cleaner ergonomics.\n- **Server Actions now support file uploads** without third-party libraries.\n\n## The minimal Server Action\n\n```typescript\n'use server'\n\nimport { z } from 'zod'\n\nconst NewPostSchema = z.object({\n  title: z.string().min(1).max(200),\n  body: z.string().min(1),\n})\n\nexport async function createPost(prev: unknown, formData: FormData) {\n  const parsed = NewPostSchema.safeParse({\n    title: formData.get('title'),\n    body: formData.get('body'),\n  })\n  if (!parsed.success) return { error: parsed.error.flatten() }\n\n  await db.post.create({ data: parsed.data })\n  return { success: true }\n}\n```\n\nThree things to notice: `'use server'` directive at the top, Zod validation as the first thing, and a structured return for the client. This is the template — every action follows it.\n\n## Auth with Server Actions\n\nThe auth-aware pattern: extract user from cookies/session in a helper, fail fast if absent, then proceed. Don't trust the client.\n\n```typescript\n'use server'\n\nexport async function deletePost(formData: FormData) {\n  const user = await getCurrentUser()\n  if (!user) throw new Error('Unauthorized')\n  \n  const postId = formData.get('postId')\n  const post = await db.post.findUnique({ where: { id: postId }})\n  if (post.authorId !== user.id) throw new Error('Forbidden')\n\n  await db.post.delete({ where: { id: postId }})\n  revalidatePath('/posts')\n}\n```\n\nThe bug we see most: forgetting to check ownership. The action runs server-side; nothing prevents a malicious user from POSTing arbitrary IDs. Always check.\n\n## `useActionState` + `useOptimistic`\n\nThe clean client pattern. `useActionState` gives you pending, error, and data states. `useOptimistic` lets you update the UI immediately while the action runs.\n\n```tsx\n'use client'\n\nconst [state, formAction, isPending] = useActionState(createPost, null)\nconst [optimisticPosts, addOptimistic] = useOptimistic(posts, (current, newPost) => [...current, newPost])\n\nreturn (\n  <form action={async (fd) => {\n    addOptimistic({ title: fd.get('title'), pending: true })\n    formAction(fd)\n  }}>\n    {/* form fields */}\n    {isPending && <Spinner />}\n    {state?.error && <ErrorBanner err={state.error} />}\n  </form>\n)\n```\n\n## Comparison: Server Actions vs Route Handlers vs tRPC\n\n| Pattern | Best for | Skip if |\n| --- | --- | --- |\n| Server Actions | Forms, mutations from your own UI | You need streaming or webhooks |\n| Route Handlers | Public APIs, webhooks, streaming | All clients are your own UI |\n| tRPC | Strong end-to-end typing across boundaries | You don't need typed RPC |\n| GraphQL | Multi-team, multi-client APIs | One team, one client |\n\n## When NOT to use Server Actions\n\n**Streaming responses.** Server Actions return JSON, not streams. Use Route Handlers with `Response.body` for token-by-token output.\n\n**Public API.** Other apps shouldn't call Server Actions. Use Route Handlers.\n\n**Webhooks.** Stripe, GitHub, etc. need stable URLs and verifiable signatures. Use Route Handlers.\n\n**Long-running tasks.** Server Actions are HTTP request handlers — they timeout at 30s on Vercel. For background work, queue a job.\n\n## Common mistakes to avoid\n\n**Skipping Zod (or equivalent) validation.** TypeScript stops at the wire. Treat the FormData as untrusted.\n\n**Forgetting `revalidatePath` or `revalidateTag`.** Mutations don't auto-invalidate caches; you have to tell Next which routes need refetching.\n\n**Using Server Actions for read-only queries.** They work, but it's the wrong tool — use server components or React Query for reads.\n\n**Not handling redirect properly.** `redirect()` in a Server Action throws — it's deliberate. Don't wrap in try/catch.\n\n**Returning errors as exceptions.** Return them as part of the action result. Exceptions hit the error boundary; structured errors give you a clean form-error UI.\n\n## FAQ\n\n**Are Server Actions slower than API routes?**\nSame performance — they're route handlers under the hood, with a different invocation pattern.\n\n**Can I use Server Actions outside Next.js?**\nNo. They're a Next.js + React feature, dependent on the framework's request handling.\n\n**What about CSRF protection?**\nBuilt-in. Next.js validates origin and same-site checks automatically.\n\n**Should I use them in App Router or Pages Router?**\nApp Router only. Pages Router doesn't support Server Actions directly.\n\n## Where to go next\n\nFor related guides see [Drizzle vs Prisma in 2026](/blog/drizzle-vs-prisma-2026), [Next.js vs Remix in 2026](/blog/nextjs-vs-remix-2026), and [TypeScript strict mode guide for 2026](/blog/typescript-strict-mode-guide-2026).","hue":165,"metaTitle":"Next.js 15 Server Actions Guide (2026 Patterns)","metaDescription":"Next.js 15 Server Actions in 2026 — auth patterns, Zod validation, optimistic UI, error handling, and when to use route handlers instead.","keywords":["nextjs 15 server actions","server actions tutorial","useactionstate","useoptimistic","server actions auth","server actions vs api routes","nextjs zod validation","nextjs 15 patterns","app router server actions","server actions error handling"],"tldr":["**Server Actions replace 80% of API routes** for form submissions, mutations, and simple RPC.","**Always validate inputs server-side with Zod.** Type-safety stops at the network boundary.","**Combine `useActionState` + `useOptimistic` for clean loading + error UI** without spinners.","**Skip Server Actions for: streaming, public APIs, third-party webhooks.** Use route handlers."],"readTime":"3 min read","wordCount":718},"related":[{"slug":"how-to-learn-nextjs-2026","title":"How to learn Next.js in 2026: App Router, Server Components, and production patterns","excerpt":"Next.js 15 with the App Router is the dominant React framework for production web apps. This 2026 guide takes you from setup to deployed full-stack application efficiently.","category":"code","date":"February 25, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":310},{"slug":"server-components-vs-server-actions-2026","title":"Server Components vs Server Actions in 2026: when to use which","excerpt":"React Server Components and Server Actions solve different problems — but developers conflate them constantly. Here is the 2026 mental model and the patterns that work in production.","category":"code","date":"February 11, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":168},{"slug":"how-to-work-with-apis-2026","title":"How to work with APIs in 2026: HTTP, auth, errors, and real patterns","excerpt":"Working with APIs is the most common programming task in 2026, yet most tutorials stop at the happy path. This guide covers auth, error handling, rate limits, and building reliable API clients.","category":"code","date":"March 18, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":312}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"multimodal-ai-tools-2026","title":"Best multimodal AI tools in 2026: vision, audio, and video compared","excerpt":"Every major AI model is multimodal now, but the gap between them for real-world tasks is still large. Here is the honest comparison across vision, audio, and video for 2026.","category":"ai","date":"February 2, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":207},"older":{"slug":"runway-gen-4-review-2026","title":"Runway Gen-4 review in 2026: production-ready video AI?","excerpt":"Runway Gen-4 is the most production-ready AI video model in 2026. We review its quality, prompt adherence, length limits, and pricing through the lens of real shipping work.","category":"ai","date":"February 2, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":200}}