Next.js Form Handling Tutorial
Updated on
Next.js provides built-in support for handling forms in both client and server components. You can use standard HTML form elements in your Next.js pages and handle form submissions using server actions or API routes.
Introduction
Form handling is a foundational aspect of any web application. This tutorial demonstrates the most common patterns for collecting and processing user input in Next.js, from simple client-side forms to secure server-side processing. Examples include login, registration, contact, and search forms suitable for both small projects and large-scale applications.
We can use form for:
- Login forms
- Registration forms
- Contact forms
- Search forms
Next.js provides multiple ways to handle forms efficiently using:
- Client Components
- Server Actions
- API Routes
Each method has its own use case.
Basic Form Example in Next.js (Client Component)
Here's a simple example of a form in a Next.js client component:
When you place "use client" at the top, the component renders in the browser and you can manage input state with React hooks. This is ideal for lightweight forms where server interaction is not required.
"use client";
import { useState } from "react"
export default function ContactForm() {
const [name, setName] = useState("")
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
console.log(name)
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Enter name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<button type="submit">
Submit
</button>
</form>
)
}
Controlled Components in Next.js
Controlled components are React components where the form data is controlled by React state. In Next.js, you can use controlled components in both client and server components.
Using useState makes it easy to validate or transform input on each keystroke and ensures the UI always reflects the current value.
"use client";
import { useState } from "react"
export default function RegisterForm() {
const [form, setForm] = useState({
name: "",
email: "",
})
const handleChange = (
e: React.ChangeEvent<HTMLInputElement>
) => {
setForm({
...form,
[e.target.name]: e.target.value,
})
}
const handleSubmit = (
e: React.FormEvent
) => {
e.preventDefault()
console.log(form)
}
return (
<form onSubmit={handleSubmit}>
<input
name="name"
placeholder="Name"
onChange={handleChange}
/>
<input
name="email"
placeholder="Email"
onChange={handleChange}
/>
<button type="submit">Register</button>
</form>
)
}
Submitting Form using API Routes
Form submission using API Routes in Next.js
API routes let you handle form data on a Next.js server endpoint, which is useful when you need to integrate with a database or third-party service.
Client Form
"use client"
export default function Form() {
const handleSubmit = async (
e: React.FormEvent<HTMLFormElement>
) => {
e.preventDefault()
const formData = new FormData(e.currentTarget)
const name = formData.get("name")
await fetch("/api/users", {
method: "POST",
body: JSON.stringify({ name }),
})
}
return (
<form onSubmit={handleSubmit}>
<input name="name" />
<button type="submit">
Submit
</button>
</form>
)
}
API Route
// app/api/users/route.ts
export async function POST(request: Request) {
const body = await request.json()
return Response.json({
message: "User saved",
data: body,
})
}
Server Actions
Server Actions, introduced in Next.js 13.4, allow you to run server-side code directly from form components. This eliminates the need to create separate API routes and simplifies data flow.
Server actions are functions that run on the server and can be called from client components. They are defined in a client component and marked with `"use server"`.
Server function written using async/await syntax. Without async/await, server actions would be synchronous and less flexible.
import { redirect } from "next/navigation";
export default function Form() {
async function saveUser(formData: FormData) {
"use server"
const name = formData.get("name");
const email = formData.get("email")
console.log(name);
redirect("/thank-you")
}
return (
<form action={saveUser}>
<input name="name" type="text"/>
<input name="email" type="email"/>
<button type="submit">Submit</button>
</form>
)
}
Advantages of Server Actions
- No API routes needed
- Server
- Better performance
- Cleaner code
In Next JS 16, Server Actions are further optimized for performance and developer experience. They are default in new projects and offer improved performance over previous versions.
Form validation
Next.js offers form validation features in server actions and API routes. You can validate form data on the server side before processing it.
"use client"
import { useState } from "react"
export default function Form() {
const [error, setError] = useState("")
const handleSubmit = (
e: React.FormEvent<HTMLFormElement>
) => {
e.preventDefault()
const formData = new FormData(e.currentTarget)
const email = formData.get("email") as string
if (!email.includes("@")) {
setError("Invalid email")
return
}
setError("")
}
return (
<form onSubmit={handleSubmit}>
<input name="email" />
{error && <p>{error}</p>}
<button type="submit">Submit</button>
</form>
)
}
Best Practices
- Validate data on the server to prevent tampering.
- Use semantic HTML elements and proper labels for accessibility.
- Leverage libraries like react-hook-form for complex forms.
- Provide clear user feedback and handle loading states.
- Keep form state minimal and reset when appropriate.
Conclusion
Handling forms in Next.js is flexible: choose client-side state for simple interactions, API routes for custom endpoints, or server actions for clean server-side logic. Following the techniques and best practices in this guide will help you build robust, scalable, and user-friendly forms in your Next.js applications.