Next.js

Client-side validation before Server Actions in Next.js

Server Actions do not replace client-side validation. Catch invalid form input early and stop unhandled errors from becoming 500 responses.

Server Actions in Next.js let you mutate data without wiring up an API route. The temptation is to treat them as a black box: collect input on the client, hand it off, and let the server sort out what is valid. I fell into this habit recently, and it bit me in production.

I had a form with a textarea for post content. On the server, I added a guard:

if (!content?.trim()) {
  throw new Error('Content is required')
}

A user submitted a string of spaces. The browser let it through because the field was not empty. The server threw. Next.js caught the unhandled error and returned a 500. The user saw a generic error page with no clue what they had done wrong, and my Vercel logs lit up with a stack trace that should never have fired.

The gap between the browser and the server

Here is the client code that caused it. A standard form handler in a client component:

function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
  e.preventDefault()
  const formData = new FormData()
  formData.set('content', content)
  startTransition(() => {
    action(formData)
  })
}

Because e.preventDefault() stops the browser's native validation, and because the form builds FormData manually, whitespace-only input slips straight through to the Server Action. The trim() guard on the server is doing the right thing, but throwing from a Server Action to reject user input is the wrong tool. It turns a validation mistake into a server crash.

Validate before the transition

The fix is an early return in handleSubmit before the FormData is even constructed:

const [error, setError] = useState<string | null>(null)

function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
  e.preventDefault()

  if (!content.trim()) {
    setError('Content is required')
    return
  }

  setError(null)

  const formData = new FormData()
  formData.set('content', content)
  startTransition(() => {
    action(formData)
  })
}

Then render the error inline, right next to the field:

{error && <p className="text-sm text-red-400">{error}</p>}

Now the user gets instant feedback. The server action is never invoked with garbage input. The 500 disappears from the logs.

Server validation is a backstop, not a front door

You still need server-side validation. Requests can be forged, and business rules should live on the server where they are harder to bypass. But server validation should catch edge cases and malicious input, not routine typos a user could have fixed themselves.

Next.js does not yet ship a built-in way to return structured validation errors from a Server Action without manually wrapping every call. Until it does, client-side guards are the most reliable way to keep error boundaries out of the hot path and give users a form that feels responsive rather than fragile.

A checklist I now use

For any form backed by a Server Action, I run through these four points before I consider it done:

Trim string inputs before checking length or emptiness. Whitespace is invisible but it is not empty.

Set local error state and return early from handleSubmit when validation fails. Do not build the payload if you already know it is bad.

Clear the error before starting the transition. Stale error messages are almost as confusing as no error message at all.

Keep the server guards, but make them return structured responses rather than throwing for expected invalid input. A throw is for exceptional failure, not for a missing field.

Conclusion

Server Actions are a convenience. They are not a replacement for client-side form validation. A trim() check and an early return in your submit handler keeps invalid data away from the server, stops unhandled errors from turning into 500s, and gives users clear feedback without ever touching an error boundary. It took me about five lines to fix, and it saved a lot of noise in production.

← Older
Deploying Next.js to Cloudflare Pages
Newer →
Building a URL Shortener with Next.js

Newsletter

A weekly newsletter on React, Next.js, AI-assisted development, and engineering. No spam, unsubscribe any time.