Master Next.js Server Actions patterns for Google Antigravity IDE full-stack development
# Next.js Server Actions Patterns for Google Antigravity IDE
Build full-stack applications with Server Actions using Google Antigravity IDE. This guide covers mutation patterns, optimistic updates, and error handling.
## Basic Server Action
```typescript
// src/app/actions/posts.ts
"use server";
import { revalidatePath, revalidateTag } from "next/cache";
import { redirect } from "next/navigation";
import { z } from "zod";
import { auth } from "@/lib/auth";
import { db } from "@/lib/db";
const createPostSchema = z.object({
title: z.string().min(5).max(200),
content: z.string().min(100),
published: z.boolean().default(false),
});
export async function createPost(formData: FormData) {
const session = await auth();
if (!session?.user) {
throw new Error("Unauthorized");
}
const rawData = {
title: formData.get("title"),
content: formData.get("content"),
published: formData.get("published") === "on",
};
const validated = createPostSchema.parse(rawData);
const post = await db.post.create({
data: {
...validated,
authorId: session.user.id,
slug: slugify(validated.title),
},
});
revalidatePath("/dashboard/posts");
revalidateTag("posts");
redirect("/posts/" + post.slug);
}
export async function deletePost(postId: string) {
const session = await auth();
if (!session?.user) {
throw new Error("Unauthorized");
}
const post = await db.post.findUnique({ where: { id: postId } });
if (!post || post.authorId !== session.user.id) {
throw new Error("Not found or unauthorized");
}
await db.post.delete({ where: { id: postId } });
revalidatePath("/dashboard/posts");
revalidateTag("posts");
}
export async function togglePublish(postId: string) {
const session = await auth();
if (!session?.user) {
throw new Error("Unauthorized");
}
const post = await db.post.findUnique({ where: { id: postId } });
if (!post || post.authorId !== session.user.id) {
throw new Error("Not found or unauthorized");
}
await db.post.update({
where: { id: postId },
data: { published: !post.published },
});
revalidatePath("/dashboard/posts");
revalidatePath("/posts/" + post.slug);
}
```
## Optimistic Updates
```typescript
// src/components/LikeButton.tsx
"use client";
import { useOptimistic, useTransition } from "react";
import { toggleLike } from "@/app/actions/likes";
import { Heart } from "lucide-react";
interface LikeButtonProps {
postId: string;
initialLiked: boolean;
initialCount: number;
}
export function LikeButton({ postId, initialLiked, initialCount }: LikeButtonProps) {
const [isPending, startTransition] = useTransition();
const [optimisticState, addOptimistic] = useOptimistic(
{ liked: initialLiked, count: initialCount },
(state, newLiked: boolean) => ({
liked: newLiked,
count: newLiked ? state.count + 1 : state.count - 1,
})
);
const handleClick = () => {
startTransition(async () => {
addOptimistic(!optimisticState.liked);
await toggleLike(postId);
});
};
return (
<button
onClick={handleClick}
disabled={isPending}
className="flex items-center gap-2"
>
<Heart className={optimisticState.liked ? "fill-red-500 text-red-500" : ""} />
<span>{optimisticState.count}</span>
</button>
);
}
```
## Form with useFormState
```typescript
// src/components/ContactForm.tsx
"use client";
import { useFormState, useFormStatus } from "react-dom";
import { submitContact } from "@/app/actions/contact";
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending} className="bg-blue-500 text-white px-4 py-2 rounded">
{pending ? "Sending..." : "Send Message"}
</button>
);
}
export function ContactForm() {
const [state, formAction] = useFormState(submitContact, { message: "", errors: {} });
return (
<form action={formAction} className="space-y-4">
{state.message && <p className={state.errors ? "text-red-500" : "text-green-500"}>{state.message}</p>}
<div>
<input name="name" placeholder="Your name" className="w-full border rounded px-3 py-2" />
{state.errors?.name && <p className="text-red-500 text-sm">{state.errors.name}</p>}
</div>
<div>
<input name="email" type="email" placeholder="Your email" className="w-full border rounded px-3 py-2" />
{state.errors?.email && <p className="text-red-500 text-sm">{state.errors.email}</p>}
</div>
<div>
<textarea name="message" placeholder="Your message" rows={4} className="w-full border rounded px-3 py-2" />
{state.errors?.message && <p className="text-red-500 text-sm">{state.errors.message}</p>}
</div>
<SubmitButton />
</form>
);
}
```
## Best Practices for Google Antigravity IDE
When using Server Actions with Google Antigravity, validate all input with Zod. Use revalidatePath and revalidateTag for cache invalidation. Implement optimistic updates for better UX. Handle errors gracefully. Let Gemini 3 generate server actions from your requirements.
Google Antigravity excels at building full-stack features with Server Actions.This Next.js prompt is ideal for developers working on:
By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their next.js implementations.
Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.
This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.
You can modify the prompt by adding specific requirements, constraints, or preferences. For Next.js projects, consider mentioning your framework version, coding style, and any specific libraries you're using.