Type-Safe Next.js Server Actions

8.5.2 · active · verified Sun Apr 19

next-safe-action is a library designed for Next.js projects to create type-safe and validated Server Actions. It leverages modern Next.js, React, and TypeScript features to ensure end-to-end type safety from client-side component calls to server-side action execution. The current stable version is 8.5.2, with minor and patch releases occurring frequently to refine types, add features, and improve developer experience. Key differentiators include robust input/output validation, a flexible middleware system for authorization or logging, advanced server error handling, and support for optimistic updates, making it a powerful tool for building reliable and predictable data mutations in Next.js applications.

Common errors

Warnings

Install

Imports

Quickstart

This quickstart demonstrates how to define a type-safe server action with input validation using Zod and then execute it from a client component using the `useAction` hook, handling loading states and displaying errors.

import { createSafeActionClient } from 'next-safe-action';
import { z } from 'zod';
import { useAction } from 'next-safe-action/hook';

// server/actions.ts
export const actionClient = createSafeActionClient();

export const addTodo = actionClient
  .schema(z.object({ text: z.string().min(1) }))
  .action(async ({ text }) => {
    // Simulate database operation
    await new Promise(resolve => setTimeout(resolve, 500));
    console.log(`Adding todo: ${text}`);
    return { success: true, newTodo: { id: Date.now(), text } };
  });

// app/page.tsx (or any client component)
'use client';

import { useState } from 'react';
import { addTodo } from '@/server/actions'; // Adjust path as needed

export default function TodoForm() {
  const [todoText, setTodoText] = useState('');
  const { execute, result, status } = useAction(addTodo, {
    onSuccess: (data) => {
      console.log('Todo added successfully:', data?.newTodo);
      setTodoText('');
    },
    onError: (error) => {
      console.error('Failed to add todo:', error);
    }
  });

  const isLoading = status === 'executing';

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      execute({ text: todoText });
    }}>
      <input
        type="text"
        value={todoText}
        onChange={(e) => setTodoText(e.target.value)}
        placeholder="New todo item"
        disabled={isLoading}
      />
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Adding...' : 'Add Todo'}
      </button>
      {result.validationErrors?.text && (
        <p style={{ color: 'red' }}>{result.validationErrors.text[0]}</p>
      )}
      {result.serverError && (
        <p style={{ color: 'red' }}>{result.serverError}</p>
      )}
    </form>
  );
}

view raw JSON →