Next.js cookbook · Intermediate

Optimistic UI with Server Actions

Make liking a post or adding a comment feel instant while the Server Action runs.

Approach

Combine a Server Action with React's useOptimistic: apply the optimistic change immediately in a transition, call the action, and let revalidation bring back the real data. If the action throws, React reverts to the server state automatically.

Solution

// app/posts/[id]/actions.ts
"use server";
import { revalidatePath } from "next/cache";

export async function addComment(postId: string, text: string) {
  const session = await verifySession();
  if (text.trim().length === 0 || text.length > 1000) throw new Error("Invalid comment");
  await db.comment.create({ data: { postId, authorId: session.userId, text } });
  revalidatePath(`/posts/${postId}`);
}

// app/posts/[id]/Comments.tsx
"use client";
import { useOptimistic, useRef, startTransition } from "react";
import { addComment } from "./actions";

export function Comments({ postId, comments }: { postId: string; comments: Comment[] }) {
  const formRef = useRef<HTMLFormElement>(null);
  const [optimistic, addOptimistic] = useOptimistic(comments, (state, text: string) => [
    ...state, { id: `temp-${Date.now()}`, text, author: "You", pending: true },
  ]);

  async function submit(formData: FormData) {
    const text = String(formData.get("text") ?? "");
    formRef.current?.reset();
    startTransition(() => addOptimistic(text));
    await addComment(postId, text);
  }

  return (
    <section>
      <ul>{optimistic.map((c) => <li key={c.id} style={{ opacity: c.pending ? 0.6 : 1 }}>{c.author}: {c.text}</li>)}</ul>
      <form ref={formRef} action={submit}><input name="text" required maxLength={1000} /><button>Post</button></form>
    </section>
  );
}

Pitfalls

  • Optimistic updates for actions likely to fail validation.
  • No visual hint that an item is pending.
  • Forgetting revalidation, so the optimistic item disappears or duplicates.

Variations

  • TanStack Query mutations with optimistic cache updates for client-fetched data.

Further reading: react.dev: useOptimistic

Reference: https://react.dev/reference/react/useOptimistic

More Next.js recipes