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
}