Server Actions
Overview
Server Actions are Next.js functions that run on the server. They're used for mutations (create, update, delete) and can be called directly from Client Components.
Characteristics
- ✅ Marked with
"use server" - ✅ Type-safe with TypeScript
- ✅ Form integration - Can be used directly in forms
- ✅ No API route needed - Direct server function call
- ✅ Automatic revalidation - Can trigger Next.js revalidation
Implementation Pattern
// actions/campaign/create-campaign-action.ts
"use server";
import { campaignFormSchema } from "@/components/forms/campaignForm/campaign.schema";
import { createClient } from "@/lib/supabase/server";
import { FormState } from "@/types/form";
export async function createCampaignAction({
formData,
teamID,
}: {
formData: campaignTypes;
teamID: string;
}): Promise<FormState> {
try {
const supabase = createClient();
// Validate with Zod
const parsed = campaignFormSchema.safeParse(formData);
if (!parsed.success) {
return {
message: "Invalid form data",
zodIssues: parsed.error.issues.map((issue) => issue.message),
error: true,
};
}
// Database operation
const { data, error } = await supabase
.from("campaigns")
.insert({ ...formData, teamID })
.select()
.single();
if (error) {
return {
message: error.message,
error: true,
};
}
return {
message: "Success",
data,
};
} catch (error) {
return {
message: "An error occurred",
error: true,
};
}
}
Using Server Actions in Forms
1. Form Component Pattern
'use client';
import { useFormState } from 'react-dom';
import { createCampaignAction } from '@/actions/campaign/create-campaign-action';
export function CampaignForm() {
const [state, formAction] = useFormState(createCampaignAction, null);
return (
<form action={formAction}>
<input name="campaignName" />
<button type="submit">Create</button>
{state?.error && <p>{state.message}</p>}
</form>
);
}
2. With React Hook Form
'use client';
import { useForm } from 'react-hook-form';
import { createCampaignAction } from '@/actions/campaign/create-campaign-action';
export function CampaignForm() {
const { register, handleSubmit } = useForm();
const onSubmit = async (data) => {
const result = await createCampaignAction({
formData: data,
teamID: params.primaryTeam,
});
if (!result.error) {
router.push(`/${params.primaryTeam}/campaign/${result.data.id}`);
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('campaignName')} />
<button type="submit">Create</button>
</form>
);
}
Server Action Categories
1. CRUD Operations
create-*-action.ts- Create operationsupdate-*-action.ts- Update operations- Located in
src/actions/
2. Authentication Actions
sign-in-action.tssign-up-action.tssign-out-action.tsprofile-update.ts
3. Search Actions
city-search.tsplace-search.tssearch-address.tsteam-search.ts
Server Action Return Types
All Server Actions return a standardized FormState type:
// types/form.ts
export interface FormState {
message?: string;
error?: boolean;
data?: any;
zodIssues?: string[]; // Validation errors
}