From harness-claude
Mutates server-side data from Next.js components using async Server Actions—no API routes needed. Ideal for forms, progressive enhancement, validation, pending states, and cache revalidation.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Mutate server-side data directly from components using async functions — no API route required
Guides Next.js Server Actions for form handling, data mutations with useFormState/useFormStatus, and revalidation via revalidatePath/Tag in App Router.
Implements Next.js Server Actions for server mutations: 'use server' definitions, form handling with useActionState/useFormStatus, optimistic updates via useOptimistic, Zod/next-safe-action validation, file uploads, authorization, and revalidation.
Validates Next.js server actions, API routes, form data with useActionState/useFormState, search params, and dynamic route segments using Zod schemas.
Share bugs, ideas, or general feedback.
Mutate server-side data directly from components using async functions — no API route required
'use server' at the top of an async function or at the top of a module containing only server functions.<form action={myAction}> for progressive enhancement — the form works without JavaScript.useFormStatus() from react-dom inside a child component to access pending state during form submission.useFormState() (renamed to useActionState() in React 19) to capture the return value of a Server Action and display validation errors.revalidatePath() or revalidateTag() after mutations to purge stale cached data.// app/actions/create-post.ts
'use server';
import { z } from 'zod';
import { revalidatePath } from 'next/cache';
const schema = z.object({ title: z.string().min(1).max(200) });
export async function createPost(prevState: unknown, formData: FormData) {
const parsed = schema.safeParse({ title: formData.get('title') });
if (!parsed.success) {
return { errors: parsed.error.flatten().fieldErrors };
}
await db.post.create({ data: { title: parsed.data.title } });
revalidatePath('/posts');
return { errors: null };
}
// app/posts/new/page.tsx
'use client';
import { useFormState, useFormStatus } from 'react-dom';
import { createPost } from '@/app/actions/create-post';
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? 'Saving...' : 'Save'}</button>;
}
export default function NewPostPage() {
const [state, action] = useFormState(createPost, null);
return (
<form action={action}>
<input name="title" />
{state?.errors?.title && <p>{state.errors.title}</p>}
<SubmitButton />
</form>
);
}
Server Actions compile to POST requests under the hood — Next.js generates a unique action ID and routes the request to the server function at runtime. This means they work in environments without JavaScript (progressive enhancement) and do not require a separate API endpoint.
Progressive enhancement: When a <form action={serverAction}> is rendered, the native HTML form submission triggers the Server Action even if client-side JS has not loaded. Enhance the experience with useFormStatus only after hydration.
Revalidation: After a mutation, the Data Cache and Full Route Cache entries for affected paths remain stale until explicitly invalidated. Call revalidatePath('/path') to purge by route or revalidateTag('tag') to purge by cache tag — both are imported from next/cache.
Error handling: Throwing inside a Server Action propagates the error to the nearest React error boundary. For user-facing validation errors, return a structured error object from the action instead of throwing.
Security: Server Actions are POST endpoints — they are subject to CSRF by nature (cookies are sent automatically by browsers). Next.js includes built-in CSRF protection via origin checking in Server Actions. Do not disable this protection.
React 19 rename: useFormState is renamed to useActionState in React 19 and moved from react-dom to react. Both work in Next.js 14/15 — prefer useActionState in new code.
https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations