Forms and Mutations
Forms enable you to create and update data in web applications. Next.js provides a powerful way to handle form submissions and data mutations using Server Actions.
Examples
How Server Actions Work
With Server Actions, you don't need to manually create API endpoints. Instead, you define asynchronous server functions that can be called directly from your components.
🎥 Watch: Learn more about forms and mutations with the App Router → YouTube (10 minutes).
Server Actions can be defined in Server Components or called from Client Components. Defining the action in a Server Component allows the form to function without JavaScript, providing progressive enhancement.
Enable Server Actions in your next.config.js
file:
module.exports = {
experimental: {
serverActions: true,
},
}
Good to know:
- Forms calling Server Actions from Server Components can function without JavaScript.
- Forms calling Server Actions from Client Components will queue submissions if JavaScript isn't loaded yet, prioritizing client hydration.
- Server Actions inherit the runtime from the page or layout they are used on.
- Server Actions work with fully static routes (including revalidating data with ISR).
Revalidating Cached Data
Server Actions integrate deeply with the Next.js caching and revalidation architecture. When a form is submitted, the Server Action can update cached data and revalidate any cache keys that should change.
Rather than being limited to a single form per route like traditional applications, Server Actions enable having multiple actions per route. Further, the browser does not need to refresh on form submission. In a single network roundtrip, Next.js can return both the updated UI and the refreshed data.
View the examples below for revalidating data from Server Actions.
Examples
Server-only Forms
To create a server-only form, define the Server Action in a Server Component. The action can either be defined inline with the "use server"
directive at the top of the function, or in a separate file with the directive at the top of the file.
export default function Page() {
async function create(formData: FormData) {
'use server'
// mutate data
// revalidate cache
}
return <form action={create}>...</form>
}
Good to know:
<form action={create}>
takes the FormData data type. In the example above, the FormData submitted via the HTMLform
is accessible in the server actioncreate
.
Revalidating Data
Server Actions allow you to invalidate the Next.js Cache on demand. You can invalidate an entire route segment with revalidatePath
:
'use server'
import { revalidatePath } from 'next/cache'
export default async function submit() {
await submitForm()
revalidatePath('/')
}
Or invalidate a specific data fetch with a cache tag using revalidateTag
:
'use server'
import { revalidateTag } from 'next/cache'
export default async function submit() {
await addPost()
revalidateTag('posts')
}
Redirecting
If you would like to redirect the user to a different route after the completion of a Server Action, you can use redirect
and any absolute or relative URL:
'use server'
import { redirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
export default async function submit() {
const id = await addPost()
revalidateTag('posts') // Update cached posts
redirect(`/post/${id}`) // Navigate to new route
}
Form Validation
We recommend using HTML validation like required
and type="email"
for basic form validation.
For more advanced server-side validation, use a schema validation library like zod to validate the structure of the parsed form data:
import { z } from 'zod'
const schema = z.object({
// ...
})
export default async function submit(formData: FormData) {
const parsed = schema.parse({
id: formData.get('id'),
})
// ...
}
Displaying Loading State
Use the useFormStatus
hook to show a loading state when a form is submitting on the server. The useFormStatus
hook can only be used as a child of a form
element using a Server Action.
For example, the following submit button:
'use client'
import { experimental_useFormStatus as useFormStatus } from 'react-dom'
export function SubmitButton() {
const { pending } = useFormStatus()
return (
<button type="submit" aria-disabled={pending}>
Add
</button>
)
}
<SubmitButton />
can then be used in a form with a Server Action:
import { SubmitButton } from '@/app/submit-button'
export default async function Home() {
return (
<form action={...}>
<input type="text" name="field-name" />
<SubmitButton />
</form>
)
}
Error Handling
Server Actions can also return serializable objects. For example, your Server Action might handle errors from creating a new item:
'use server'
export async function createTodo(prevState: any, formData: FormData) {
try {
await createItem(formData.get('todo'))
return revalidatePath('/')
} catch (e) {
return { message: 'Failed to create' }
}
}
Then, from a Client Component, you can read this value and display an error message.
'use client'
import { experimental_useFormState as useFormState } from 'react-dom'
import { experimental_useFormStatus as useFormStatus } from 'react-dom'
import { createTodo } from '@/app/actions'
const initialState = {
message: null,
}
function SubmitButton() {
const { pending } = useFormStatus()
return (
<button type="submit" aria-disabled={pending}>
Add
</button>
)
}
export function AddForm() {
const [state, formAction] = useFormState(createTodo, initialState)
return (
<form action={formAction}>
<label htmlFor="todo">Enter Task</label>
<input type="text" id="todo" name="todo" required />
<SubmitButton />
<p aria-live="polite" className="sr-only">
{state?.message}
</p>
</form>
)
}
Optimistic Updates
Use useOptimistic
to optimistically update the UI before the Server Action finishes, rather than waiting for the response:
'use client'
import { experimental_useOptimistic as useOptimistic } from 'react'
import { send } from './actions'
type Message = {
message: string
}
export function Thread({ messages }: { messages: Message[] }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic<Message[]>(
messages,
(state: Message[], newMessage: string) => [
...state,
{ message: newMessage },
]
)
return (
<div>
{optimisticMessages.map((m, k) => (
<div key={k}>{m.message}</div>
))}
<form
action={async (formData: FormData) => {
const message = formData.get('message')
addOptimisticMessage(message)
await send(message)
}}
>
<input type="text" name="message" />
<button type="submit">Send</button>
</form>
</div>
)
}
Setting Cookies
You can set cookies inside a Server Action using the cookies
function:
'use server'
import { cookies } from 'next/headers'
export async function create() {
const cart = await createCart()
cookies().set('cartId', cart.id)
}
Reading Cookies
You can read cookies inside a Server Action using the cookies
function:
'use server'
import { cookies } from 'next/headers'
export async function read() {
const auth = cookies().get('authorization')?.value
// ...
}
Deleting Cookies
You can delete cookies inside a Server Action using the cookies
function:
'use server'
import { cookies } from 'next/headers'
export async function delete() {
cookies().delete('name')
// ...
}
See additional examples for deleting cookies from Server Actions.
Was this helpful?