Skills next-safe-action
install
source · Clone the upstream repo
git clone https://github.com/TerminalSkills/skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/TerminalSkills/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/next-safe-action" ~/.claude/skills/terminalskills-skills-next-safe-action && rm -rf "$T"
manifest:
skills/next-safe-action/SKILL.mdsource content
next-safe-action
Overview
next-safe-action adds type safety, input validation, and middleware to Next.js Server Actions. Instead of manually parsing FormData and handling errors, define a Zod schema and get validated, typed inputs with automatic error handling.
Instructions
Step 1: Setup
npm install next-safe-action zod
// lib/safe-action.ts — Action client with auth middleware import { createSafeActionClient } from 'next-safe-action' import { auth } from '@/auth' // Public actions (no auth required) export const publicAction = createSafeActionClient() // Authenticated actions export const authAction = createSafeActionClient({ async middleware() { const session = await auth() if (!session?.user) throw new Error('Not authenticated') return { user: session.user } }, })
Step 2: Define Actions
// actions/projects.ts — Type-safe server actions 'use server' import { authAction } from '@/lib/safe-action' import { z } from 'zod' import { prisma } from '@/lib/db' import { revalidatePath } from 'next/cache' const createProjectSchema = z.object({ name: z.string().min(1).max(100), description: z.string().max(500).optional(), }) export const createProject = authAction .schema(createProjectSchema) .action(async ({ parsedInput, ctx }) => { const project = await prisma.project.create({ data: { ...parsedInput, ownerId: ctx.user.id, }, }) revalidatePath('/dashboard') return { project } }) const updateProjectSchema = z.object({ id: z.string().uuid(), name: z.string().min(1).max(100).optional(), description: z.string().max(500).optional(), status: z.enum(['active', 'archived']).optional(), }) export const updateProject = authAction .schema(updateProjectSchema) .action(async ({ parsedInput, ctx }) => { const { id, ...data } = parsedInput // Verify ownership const project = await prisma.project.findFirst({ where: { id, ownerId: ctx.user.id }, }) if (!project) throw new Error('Project not found') const updated = await prisma.project.update({ where: { id }, data, }) revalidatePath(`/projects/${id}`) return { project: updated } })
Step 3: Use in Components
// components/CreateProjectForm.tsx — Form with safe action 'use client' import { useAction } from 'next-safe-action/hooks' import { createProject } from '@/actions/projects' export function CreateProjectForm() { const { execute, result, isExecuting } = useAction(createProject) return ( <form action={execute}> <input name="name" placeholder="Project name" required /> <textarea name="description" placeholder="Description (optional)" /> {result.validationErrors && ( <div className="errors"> {Object.entries(result.validationErrors).map(([field, errors]) => ( <p key={field}>{field}: {errors?.join(', ')}</p> ))} </div> )} {result.serverError && ( <p className="error">{result.serverError}</p> )} <button disabled={isExecuting}> {isExecuting ? 'Creating...' : 'Create Project'} </button> </form> ) }
Guidelines
- Always use Zod schemas for input validation — never trust client-submitted data.
- Use middleware for authentication — runs before every action in the chain.
hook providesuseAction
,isExecuting
, and automatic error handling.result- Combine with
for instant UI feedback.useOptimisticAction - Revalidate paths/tags after mutations to keep the UI in sync with the database.