Form Validation and Feedback: A Practical Guide for Next.js
Learn to implement secure server-side validation in Next.js. Master returning feedback to the client and displaying error messages to improve data integrity.
Previously in this course, we covered Building a Newsletter Sign-up Form in Next.js using Server Actions. While that setup handles form submission, it lacks a critical piece of production-ready software: ensuring the data entering your system is actually valid.
In this lesson, we are going to move beyond "happy path" programming. You'll learn how to validate inputs on the server, return structured feedback to the client, and display those errors so your users know exactly what went wrong.
Why Server-Side Validation is Essential
Validation is the primary gatekeeper for your application's security and data integrity. While client-side validation (like the required attribute on an <input>) is great for UX, it is easily bypassed by bots or malicious actors.
By performing validation within your Server Actions, you ensure that regardless of how the request is sent, your database only ever sees clean, expected data.
Implementing Server-Side Validation

To validate inputs, we need a way to check the data against a set of rules. A common pattern in the Node.js ecosystem is to return a "state" object from our Server Action.
Let's update our newsletter sign-up action to include basic validation.
TYPESCRIPTCE9178">'use server' export async function subscribeToNewsletter(prevState: any, formData: FormData) { const email = formData.get(CE9178">'email') as string; // Simple validation logic if (!email || !email.includes(CE9178">'@')) { return { success: false, message: CE9178">'Please provide a valid email address.', errors: { email: CE9178">'Invalid email format' } }; } // Simulate database logic console.log(CE9178">'Subscribing:', email); return { success: true, message: CE9178">'Successfully subscribed!' }; }
Providing Feedback to the Client
Next.js provides the useFormState hook (now often referred to as useActionState in newer versions) to handle the returned state from your action. This allows your component to react to the server's response.
You'll need to wrap your form component in "use client" to utilize this hook.
TSXCE9178">'use client' import { useActionState } from CE9178">'react'; import { subscribeToNewsletter } from CE9178">'./actions'; export default function NewsletterForm() { const [state, formAction] = useActionState(subscribeToNewsletter, null); return ( <form action={formAction}> <input name="email" type="email" /> {state?.errors?.email && <p className="text-red-500">{state.errors.email}</p>} {!state?.success && state?.message && <p>{state.message}</p>} {state?.success && <p className="text-green-500">{state.message}</p>} <button type="submit">Subscribe</button> </form> ); }
Common Pitfalls
When implementing validation, keep these three traps in mind:
- Trusting the Client: Never assume that because an input has a
requiredattribute, the data arriving at your Server Action is present. Always validate on the server. - Generic Error Messages: Telling a user "Something went wrong" is frustrating. Be specific—tell them which field failed and why.
- Forgetting Accessibility: Ensure your error messages are associated with the input fields using
aria-describedbyso screen readers announce the error when a user focuses on the field.
Hands-on Exercise
Refactor your existing newsletter form:
- Update your action to return a formal
errorsobject. - Implement the
useActionStatehook in your component. - Display error messages conditionally below the relevant input field.
- Add an extra validation rule, such as checking if the email length is at least 5 characters.
FAQ
Q: Should I still use client-side validation? A: Absolutely. Use HTML5 attributes or libraries like Zod for real-time, snappy user feedback, but always treat it as a secondary layer. Your server is the final source of truth.
Q: Can I use libraries for validation?
A: Yes. Many production apps use Zod to define schemas and validate FormData objects concisely. It prevents the manual if/else boilerplate we used above.
Q: Where should I display the error? A: Place the error message as close to the relevant input as possible to maintain context for the user.
Recap

In this lesson, we established that validation is a non-negotiable part of secure development. By returning structured state from Server Actions and consuming it with useActionState, we create a resilient bridge between our user's intent and our data storage. These patterns are essential for avoiding Secure Multi-Part Form Data vulnerabilities and maintaining a high-quality user experience.
Up next: We’ll move into the data layer by performing a Database Setup with Prisma.
Work with me

Next.js Full-Stack Web App Development
A fast, SEO-ready full-stack web app built with Next.js 16 — from idea to deployed product, by an engineer who ships to production.

Next.js E-commerce Store Development
Turn your Facebook page or small shop into a real online store — fast, mobile-first, and built to sell. Own your storefront, not just a social page.
