Data & Mutations

Interview Questions: Server Actions, Caching, and Revalidation

Q1: What are Server Actions and how do they differ from API Routes?

Server Actions: Asynchronous functions executed on the server, callable directly from components (Server or Client). They are designed for mutations (POST requests) and form handling.

API Routes: Traditional REST endpoints (route.js). Useful for webhooks, public APIs, or complex request handling.

Advantage: Server Actions reduce boilerplate (no need to manually fetch('/api/...')) and integrate tightly with Next.js caching (revalidatePath).

Q2: How does Caching work in the App Router?

Next.js has a multi-layer caching system:

  1. Request Memoization: Deduplicates identical fetch requests within a single render pass (React feature).
  2. Data Cache: Persists fetch results across requests (Server feature). Controlled via next: { revalidate: 3600 }.
  3. Full Route Cache: Caches the rendered HTML and RSC payload of static routes at build time.
  4. Router Cache: Client-side cache of visited routes for instant navigation.

Q3: How do you handle form validation and errors in Server Actions?

Since Server Actions are just functions, you can return plain objects.

'use server'
import { z } from 'zod'

const schema = z.object({ email: z.string().email() })

export async function subscribe(prevState, formData) {
  const result = schema.safeParse({ email: formData.get('email') })
  
  if (!result.success) {
    return { errors: result.error.flatten().fieldErrors }
  }
  
  // ... database logic ...
  return { message: 'Success!' }
}

On the client, use useFormState (React 18) or useActionState (React 19) to handle the return value.

Q4: What is the difference between revalidatePath and revalidateTag?

revalidatePath('/blog'): Purges the cache for a specific URL path.

revalidateTag('posts'): Purges all data fetches tagged with ['posts'], regardless of which page they are on. This is more granular and powerful for shared data.

Q5: How do Streaming and Suspense improve data fetching UX?

Problem: Traditionally, the server waits for all data to be fetched before sending any HTML (Waterfall).

Solution: Wrap slow components in <Suspense>.

<Suspense fallback={<LoadingSkeleton />}>
  <SlowComponent />
</Suspense>

Result: Next.js instantly sends the initial HTML (shell). When the slow component finishes fetching, its HTML is streamed in via a separate chunk, replacing the fallback.

Q6: When should you use Client-side Fetching (SWR/TanStack Query) in the App Router?

While Server Components are preferred for initial data load, client-side fetching is better for:

  • Real-time data: Polling or live updates (e.g., stock prices, chat).
  • User-specific interactions: "Like" buttons, infinite scrolling, or search-as-you-type.
  • Browser-only APIs: Data that depends on window or localStorage.

Q7: How do you perform Parallel Data Fetching in Server Components?

Sequential (Slow): Awaiting requests one by one creates a waterfall.

// ❌ Waits for User, THEN waits for Posts
const user = await getUser();
const posts = await getPosts();

Parallel (Fast): Initiate both requests simultaneously.

// ✅ Both start at the same time
const userData = getUser();
const postsData = getPosts();

// Wait for both to finish
const [user, posts] = await Promise.all([userData, postsData]);