Server Action Patterns
The Template
Every server action should follow this pattern:
"use server"
import { z } from "zod"
import { requireAuth } from "@/lib/action-auth"
import { createAdminClient } from "@/lib/supabase/admin"
import { revalidatePath } from "next/cache"
const InputSchema = z.object({
title: z.string().min(1).max(200),
category: z.enum(["security", "performance", "architecture", "quality"]),
})
export async function createRule(input: unknown) {
// 1. Auth
const { orgId, userId } = await requireAuth()
// 2. Validate
const data = InputSchema.parse(input)
// 3. Mutate
const admin = createAdminClient()
const { data: rule, error } = await admin
.from("rules")
.insert({ ...data, organization_id: orgId, created_by: userId })
.select()
.single()
if (error) {
return { success: false, error: error.message }
}
// 4. Revalidate
revalidatePath("/rules")
// 5. Return
return { success: true, data: rule }
}
Key Rules
- requireAuth() first — always
- Validate input — never trust FormData
- Scope to org — always include organization_id
- Admin client for writes — createClient for reads
- Revalidate all paths — not just the current page
- Return structured results — { success, data/error }