Back to Articles
Technology
5 min read August 2026

How to Structure Server Actions & TanStack Query in Next.js

A clean architectural pattern for handling optimistic mutations, server state invalidation, and type-safe Zod validation.

Ma
Marcus Vance
Principal Web Architect

# How to Structure Server Actions & TanStack Query in Next.js

Modern full-stack React architecture in Next.js App Router presents a powerful question: when should you use native Server Actions directly, and when should you orchestrate them with TanStack Query?

The Mental Model

Server Actions execute strictly on the server, benefiting from direct access to databases, secrets, and session cookies without exposing internal API endpoints. However, client-side coordination — such as optimistic state rollbacks, retry backoff, and caching — is often best managed by a dedicated query manager.

1. Defensive Server Action Implementation

Every mutation must be verified against Murphy's Law:

- **Validate inputs with Zod** before executing any query. - **Authorize the active session** on the server — never trust client headers. - **Ensure idempotency** by accepting an idempotency token to prevent double-click errors.

export async function enrollStudentAction(workshopId: string, idempotencyKey: string) {
  const session = await getVerifiedSession();
  if (!session) throw new Error("Unauthorized");
  
  // Guard against duplicate mutations
  const existing = await findIdempotentKey(idempotencyKey);
  if (existing) return existing.result;
  
  return await db.enrollments.create({ data: { workshopId, userId: session.user.id } });
}

Optimistic Updates & Safe Rollback

When a user taps "Enroll Now" or "Submit Project", the UI should respond immediately with an optimistic status pill, while safely reverting to previous state if the mutation encounters a network interruption.

const queryClient = useQueryClient();
const mutation = useMutation({
  mutationFn: enrollStudentAction,
  onMutate: async (newEnrollment) => {
    await queryClient.cancelQueries({ queryKey: ['enrollments'] });
    const previous = queryClient.getQueryData(['enrollments']);
    queryClient.setQueryData(['enrollments'], (old) => [...old, newEnrollment]);
    return { previous };
  },
  onError: (err, newEnrollment, context) => {
    queryClient.setQueryData(['enrollments'], context?.previous);
  },
  onSettled: () => {
    queryClient.invalidateQueries({ queryKey: ['enrollments'] });
  },
});

Practical Application

We explore this pattern end-to-end in the **Next.js 16 Production Architecture & Turbopack Workshop**, where students build a production-grade education dashboard with zero layout shift.

#Next.js
#Architecture
#React 19
#TanStack Query
Connected Workshop Program

Next.js 16 Production Architecture & Turbopack

Architect high-performance web applications using Server Components, caching strategies, and streaming SSR.

Mentor: Marcus Vance • 4h Live Cohort
View Workshop Details

Reader Discussion (1)

Alex Taylor2 days ago

This breakdown of Server Actions vs TanStack Query really clarified optimistic rollback mechanics. Looking forward to applying it in the workshop project!