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:
- Request Memoization: Deduplicates identical fetch requests within a single render pass (React feature).
- Data Cache: Persists fetch results across requests (Server feature). Controlled via
next: { revalidate: 3600 }. - Full Route Cache: Caches the rendered HTML and RSC payload of static routes at build time.
- 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
windoworlocalStorage.
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]);