React Hook Form with Next.js Server Actions splits the work: the library manages field registration and client-side validation, while the Server Action runs the server write. You pass the action to React Hook Form's Form component, which validates first and then calls it.
You reach for this combination when a form has enough fields that hand-wiring controlled state becomes noisy. The library keeps the client side tidy, and the action keeps the server write in one place.
Install the library first, then create the Server Action the form will submit to.
npm install react-hook-formThe install adds the react-hook-form package, and the useForm hook plus the Form component come from it. Passing a function to the Form component's action prop needs version 7.84 or later, so check your lockfile if the action never runs. Next, create the Server Action that reads the submitted fields and writes them on the server.
// app/actions.ts
'use server'
import { db } from '@/lib/db'
export async function signUp(formData: FormData) {
const name = String(formData.get('name') ?? '')
if (name.length < 2) return
await db.user.create({ data: { name } })
}The action stays a plain Server Action. React Hook Form does not change how the server side works, only how the client registers and validates the fields, so the same auth and validation rules apply here as in any other action.
Now build the form. Save this as app/signup-form.tsx. React Hook Form registers each input with its rules, and the Form component passes the action to the underlying form element.
'use client'
import { useForm, Form } from 'react-hook-form'
import { signUp } from './actions'
export function SignupForm() {
const { control, register, formState: { errors } } = useForm<{ name: string }>()
return <Form action={signUp} control={control}>
<label>Name <input {...register('name', { required: 'Name is required' })} /></label>
{errors.name && <p role="alert">{errors.name.message}</p>}
<button type="submit">Sign up</button>
</Form>
}The register call wires the input and its rule, and errors.name shows the message when the rule fails. The component is a Client Component because both hooks run in the browser.
Give useForm a type argument for your fields. Without it the message is a wider union than a string, and rendering it directly is a TypeScript error rather than a runtime one.
Client validation runs first
React Hook Form checks the registered rules before the form submits. When name is empty, the message appears and the action never runs. That is the client-side convenience layer.
It does not replace server-side checks. The action is reachable over HTTP, so validate inside it the same way a plain Server Action form would. Keep the client rules short and put the authoritative rules in a schema or directly in the action.
Registering a field also gives React Hook Form the ref it needs for focus management, so a failed rule can move focus to the first invalid input automatically.
Server errors come back separately
The action's return value does not flow into the register errors automatically. Read it with useActionState and render it next to the field, or use React Hook Form's setError to merge server messages into the same error state.
Merging them into the same errors object keeps the UI consistent, because every message then renders through one path instead of two.
One difference is worth knowing. The FormData your action receives is rebuilt by React Hook Form from the values it validated, not lifted straight off the DOM form, so an input that was never registered does not reach the server.
This form also needs JavaScript. React Hook Form validates in a submit handler and calls the action itself, so unlike a plain Server Action form it does not submit before hydration. Use a plain form when working without JavaScript matters more than client-side validation.
For the visual half, see displaying field-level validation errors from a Server Action, and building forms in Next.js with Server Actions for the base pattern.
Rune AI
Key Insights
- Install react-hook-form and import useForm and Form.
- Register each input with its validation rules.
- Pass the Server Action to the Form action prop.
- Show errors from formState.errors.
- Validate again inside the action on the server.
Frequently Asked Questions
What does React Hook Form handle here?
Can the form component take a Server Action?
Do I still validate on the server?
Conclusion
React Hook Form and Server Actions split the work: the library registers fields and validates on the client, while the action runs the server write. Pass the action to the Form component and keep server-side validation as the real guard.
More in this topic
`generateMetadata` Explained with Real Examples
What generateMetadata does, when it runs, and how to use it for real routes: awaited params, deduplicated data fetching, extending parent metadata, and returning a 404 from metadata.
Canonical URLs in Next.js: `metadataBase`, `alternates.canonical`, and Dynamic Pages
How canonical URLs work in the Next.js App Router: setting metadataBase once, writing alternates.canonical per route, handling dynamic segments, and what happens when the base URL is missing.
Open Graph and Twitter Card Metadata in Next.js
How to write Open Graph and Twitter card metadata in the Next.js App Router: the openGraph and twitter fields, automatic card defaults, article tags, and image merge rules.