Frontend

Next.js 14 and Server Actions

How Server Actions actually work, why a single request carries back both the mutation and fresh UI, and the security checks you still have to do yourself.

Aref Busak
Aref BusakAuthor
2024-06-223 min read6 views
Next.js 14 and Server Actions

What Are Server Actions?

A Server Action is an async function that runs on the server instead of the browser. Next.js invokes it through React's action mechanisms — most commonly <form action>. You define one by adding the 'use server' directive, either at the top of the function or at the top of a dedicated file:

'use server'

import { revalidatePath } from 'next/cache'

export async function createPost(formData) {
  await db.post.create({
    data: { title: formData.get('title') },
  })
  revalidatePath('/posts')
}

You can pass it straight to a form's action prop, with no separate API route needed:

<form action={createPost}>
  <input name="title" />
  <button type="submit">Submit</button>
</form>

One Request, Data and UI Together

When an action calls revalidatePath, updateTag, or redirect, Next.js runs the mutation and re-renders the current route on the server within the same HTTP request. The response carries both the action's return value and a freshly rendered RSC Payload, so the client shows the updated UI without an extra round trip.

Sequential Dispatch on the Client

Next.js dispatches Server Actions from a given client one at a time, not in parallel. If a user triggers several actions back to back, each waits for the previous one to finish. If you need real parallelism, do it inside a single action or fetch in parallel from a Server Component.

Security Is Still Your Job

Every Server Action is effectively a public POST endpoint — anyone who can send the matching request can call it, whether or not they went through your UI. Next.js includes some built-in protections (an Origin/Host based CSRF check, a body size limit, encrypted action IDs), but none of that replaces your own checks. Inside every action you should still:

  • Authenticate and authorize the caller (conditionally rendering a form is not a security boundary)
  • Validate the incoming FormData, since it's entirely untrusted
  • Return only what the UI needs, not raw database rows

Choosing How to Refresh the Cache

A few options exist for refreshing data after a mutation:

  • updateTag — invalidates immediately; the current response waits for fresh data
  • revalidateTag — stale-while-revalidate; the current user still sees the old value while a refresh happens in the background
  • revalidatePath — the simplest option when only one route is affected

Takeaway

Server Actions shorten the path from form to mutation: no separate API route, baseline security from the framework, and UI that updates in the same round trip. None of that replaces authenticating and validating input inside the action itself.

Source: Next.js documentation — Server Actions

Frontend
Aref Busak
Aref Busak

Better web, better design — fast, modern, results-driven websites.