- Next.js
- Server Actions & Form Mutations
- Server Action Closures, Revalidation, and Redirection
Server Action Closures, Revalidation, and Redirection
Server Actions can close over server-side scope and receive bound parameters via Function.prototype.bind. After executing mutations, Server Actions orchestrate data updates through revalidatePath/revalidateTag and page navigation via redirect(). Critically, redirect() internally throws a NEXT_REDIRECT exception that must not be caught by unhandled try/catch blocks.
Learning Objectives
Pass bound variables securely to Server Actions using the .bind() pattern.
Execute sequential cache invalidation across multiple tags and paths.
Understand why redirect() throws an internal NEXT_REDIRECT error and how to structure try/catch safely.
Control permanent vs temporary redirects from Server Actions.
Prerequisites & Background Knowledge
Why Interviewers Care
The redirect-inside-try-catch trap is one of the most common mistakes made in Next.js coding interviews.
The Challenge
Explain the closure and binding patterns used with Server Actions. Demonstrate how to update an entity, revalidate cached pages, and redirect the user while properly handling the NEXT_REDIRECT exception.
Code Architecture & Implementation
1// app/actions/articles.ts2'use server';34import { revalidatePath, revalidateTag } from 'next/cache';5import { redirect } from 'next/navigation';6import { db } from '@/lib/db';78// Using .bind() allows passing server-managed variables (like articleId)9// without creating hidden inputs in the client HTML10export async function updateArticleAction(articleId: string, formData: FormData) {11 const title = formData.get('title') as string;12 const content = formData.get('content') as string;1314 try {15 // 1. Database mutation16 await db.articles.update({17 where: { id: articleId },18 data: { title, content },19 });2021 // 2. Surgical cache revalidation22 revalidateTag(`article-${articleId}`);23 revalidatePath('/articles');24 } catch (err) {25 console.error('Database update failed:', err);26 return { error: 'Failed to update article. Please try again.' };27 }2829 // 3. IMPORTANT: redirect() must be called OUTSIDE the try/catch block!30 // redirect() works by throwing an internal NEXT_REDIRECT error.31 redirect(`/articles/${articleId}`);32}
Architectural Explanation
In Next.js, redirect() does not immediately send an HTTP 302 response header. Instead, it throws a special control-flow error: NEXT_REDIRECT. The Next.js framework catches this error at the top-level handler to initiate navigation. If developers wrap the entire action in try { ... redirect(); } catch (err) { return err; }, the catch block intercepts NEXT_REDIRECT, treats it as a database failure, and aborts navigation. Always place redirect() after the try/catch block.
Common Mistakes & Anti-Patterns
Calling redirect() inside a catch-all try/catch block, breaking redirection entirely.
Using hidden `<input type="hidden" name="id" value={id}>` for sensitive IDs instead of `.bind()`, allowing malicious users to edit someone else's entity.
Forgetting that revalidatePath must be called before redirect(), because code after redirect() is unreachable.
Interview Follow-Up Traps
The interviewer asks: "Why does redirect() throw an error instead of returning a response object?" Because throwing an error immediately halts the execution of subsequent code in the function without requiring explicit return statements.
Frequently Asked Questions
Does redirect() issue a 307 or 303 status code?
Server Actions use HTTP 303 (See Other) redirection to ensure the browser follows the redirect with an HTTP GET request.
Can you use permanentRedirect() instead?
Yes! `permanentRedirect()` issues a 308 permanent redirect, commonly used for URL slug changes.
