Instruction file imported from samipsamip/linkshortnerproject (
.github/instructions/server-actions.instructions.md). Copyright stays with the author.
Server Actions
Rules
- All data mutations must use server actions. Never mutate data via API routes or directly in components.
- Server actions must be called from client components only.
- Server action files must be named
actions.tsand colocated in the same directory as the component that calls them. - Never use
FormDataas a TypeScript type. Define explicit TypeScript types for all action inputs. - Validate all inputs with Zod before any business logic or database operations.
- Always verify a logged-in user first. Use
auth()from@clerk/nextjs/serverand return early if nouserIdis present. - Never call Drizzle directly in server actions. Use helper functions from the
/datadirectory for all database operations. - Never throw errors. Return a typed object with either a
successorerrorproperty instead.
Pattern
// app/dashboard/actions.ts
"use server";
import { z } from "zod";
import { auth } from "@clerk/nextjs/server";
import { createLink } from "@/data/links";
const CreateLinkSchema = z.object({
url: z.string().url(),
shortCode: z.string().min(1),
});
type CreateLinkInput = z.infer<typeof CreateLinkSchema>;
export async function createLinkAction(
input: CreateLinkInput,
): Promise<{ success: true } | { error: string }> {
const { userId } = await auth();
if (!userId) return { error: "Unauthorized" };
const parsed = CreateLinkSchema.safeParse(input);
if (!parsed.success)
return { error: parsed.error.flatten().formErrors.join(", ") };
await createLink({ ...parsed.data, clerkUserId: userId });
return { success: true };
}