Skip to main content

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 operations
  • update-*-action.ts - Update operations
  • Located in src/actions/

2. Authentication Actions​

  • sign-in-action.ts
  • sign-up-action.ts
  • sign-out-action.ts
  • profile-update.ts

3. Search Actions​

  • city-search.ts
  • place-search.ts
  • search-address.ts
  • team-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
}