Next.js
is a React-based framework for building scalable, performant, and optimized full-stack web applications. With a minimal boundary between the client-side and server-side, it's incredibly easy to transfer data from your backend to the frontend and vice versa.
Click here to jump straight to the implementation.
The Birth of NextJS Server Actions
Traditionally, communicating with the server-side in NextJS required creating an API route. However, with the release of NextJS 13 in October 2022, Vercel introduced an experimental concept known as 'Server Actions'.
A year later, in October 2023, the launch of NextJS 14 marked Server Actions as a stable feature. The initial reception of Server Actions was not great, resulting in a wave of memes and jokes about how 'ridiculous' the feature was. One particular image from the NextJS 14 presentation was widely circulated.
For many developers, the idea of integrating SQL within a ReactJS component seemed absurd. And indeed, from a developer's perspective, I concur. However, I believe the criticism Server Actions received was not entirely justified.
Obviously, during the Vercel presentation, they used this piece of code to give an example of what Server Actions are and how you can use them. In my opinion, they did a great job in shipping the message.
A few months later, I absolutely fell in love with Server Actions. When I need to pass data from the client to my server, I simply create a function annotated with the use server
flag and call it from my client component. This eliminates the need for a full API endpoint, greatly enhancing code readability and development speed. However there's one critical aspect of Server Actions that is often overlooked.
The Problem With Server Actions
Since Server Actions are essentially compiled into 'regular' API routes by NextJS, it's crucial to validate all incoming data before it flows into your server side logic.
Many developers use Zod to validate input data on the front end before sending it to the backend via a Server Action. While this is a good practice, it leaves your Server Actions—or essentially your API routes—still vulnerable to invalid data, allowing bad-actors to directly send any data they wish to the server side of your application.
Implementing (copying) the same Zod schema from your client component into your server action is a solution. However, as lazy developers, we do not want to repeat ourselves (keep it DRY!).
The Solution
The solution to this problem is to migrate the Zod schema, which validates user input, directly into your Server Action and ship all validation errors back to your Client Component. This results into your Server Action being fully secure, while still providing the capability to display appropriate input validation errors on the front-end based on the Zod results. This is exactly what next-server-action-validation solves.
Implementing next-server-action-validation
Let's get started!
Installation
npm install next-server-action-validation zod
Usage
This package uses Zod for runtime validation of data passed into Server Actions
Step 1: Protect your Server Action
To protect your server action it needs to be wrapped in the withValidation
function together with a Zod.js validation schema.
// server-action.ts
'use server';
import * as z from 'zod';
import { withValidation } from 'next-server-action-validation';
// 1. Create the Validation Schema
const myServerActionSchema = z.object({
name: z.string().min(4)
});
// 2. Wrap your server action in the `withValidation` function
export const myServerAction = withValidation(myServerActionSchema, async (data: { name: string }) => {
console.log(data); // Data is fully typesafe
return true;
});
Step 2: Use your Server Action
To use your server action and check for validation errors we can simply call our action and pass its result into the isValidationError
function. If this function resolves to be truthy the validation failed. From now on the type of our result is a ValidationError
which holds all validation errors.
// page.tsx
'use client';
import { useState } from 'react';
import { myServerAction } from '@/app/page.actions';
import { isValidationError } from 'next-server-action-validation';
export default function Home() {
const [myString, setMyString] = useState('');
async function handleSave() {
// 1. Call your server action as normal
const result = await myServerAction({ name: myString });
// 2. Check for validation errors
if (isValidationError(result)) {
// The type of result.errors is an array of `ZodIssues`
// which we can use to display proper error messages
console.log(result.errors);
// 3. Return out of the function
return;
}
// 4. Proceed as normal
console.log('We passed correct data!');
}
return (
<main>
<Input onChange={e => setMyString(e.target.value)} />
<Button onClick={handleSave}>Save</Button>
</main>
);
}
Conclusion
In conclusion, next-server-action-validation
bridges a crucial gap in NextJS Server Actions by securely integrating Zod schema validation directly into server-side processing. This approach not only fortifies your Server Actions against invalid data but also maintains the ease of displaying validation errors on the client side.
By using this package, you can enhance your NextJS applications' security without sacrificing development speed or violating the DRY principle. It's a step forward in making full-stack development with NextJS more robust and efficient.
Top comments (0)