Verified against Claude Code · 2026-07-18
Build a Server Action form with real validation, not a client fetch call in disguise
Produces a form wired to a Server Action with schema validation, per-field errors, and useActionState-driven pending/error UI instead of hand-rolled loading state.
The prompt
Ready to copy — highlighted parts are example details you can swap.
<context>
Form fields: displayName (text, required), bio (textarea, optional, max 280 chars)
What the submission actually does (the mutation): Updates the current user's profile row in the database
Validation library preference: Zod
</context>
<task>
Build this as a real Server Action form: a <form action=(serverAction)> whose action prop is a function marked 'use server', not a client-side onSubmit handler that fetches an API route. Wire up pending and error state with useActionState (and useFormStatus for the submit button) instead of manual isLoading/error useState.
</task>
<requirements>
- Validate the submitted FormData against a schema server-side, inside the Server Action itself — never trust client-side validation alone, since the action can be invoked directly.
- Return field-level errors from the action in the shape useActionState expects, so each input can show its own error message instead of one generic banner.
- After a successful mutation, call revalidatePath (or revalidateTag) for whatever page/data this action affects, so the UI reflects the change without a manual client-side refetch.
- Keep the submit button disabled and showing a pending label via useFormStatus while the action is in flight — don't hand-roll this with a separate useState your Server Action has to remember to update.
- The form should still submit successfully with JavaScript disabled, since it's a real HTML form action — don't add anything (like preventDefault + fetch) that would break that.
</requirements>
<output_format>
Two files: the Server Action ('use server' function with validation), and the form component using useActionState/useFormStatus. Then one line confirming what gets revalidated on success.
</output_format>Customize the highlighted detailsoptional — the prompt above already works
Why this works
The load-bearing distinction here is that a Server Action attached directly to a form's action prop is a real HTML form submission the browser can execute natively — that's what makes it work with JavaScript disabled, and it's also exactly the property a client-side onSubmit-plus-fetch reimplementation throws away for no benefit. Requiring server-side validation inside the action itself (not just in the client form) matters because a Server Action is a callable server endpoint the client can invoke directly, bypassing whatever validation lives in the form component. And routing pending/error state through useActionState and useFormStatus instead of hand-rolled useState removes an entire class of bugs where the loading spinner and the actual action state drift out of sync — the hook is wired directly to the action's lifecycle, not to a variable the developer has to remember to flip.
What you get back
Submitting a bio over 280 characters returns { errors: { bio: 'Must be 280 characters or fewer' } } from the action, which useActionState surfaces under the bio field specifically — the displayName field shows no error and its value is preserved. On success, revalidatePath('/profile') runs inside the action so the profile page shows the new bio immediately.
Verified against
Claude Code Sonnet 4.6 · 2026-07-18
v0 by Vercel 2026.6 · 2026-07-19
Changelog
- 2026-07-18 — Initial version, tested against Claude Code and v0 on React 19 / Next.js 16 forms.
Need this built into your business?
If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.
EXPLORE CUSTOM SOFTWARE

