A redirect sends the client to a new URL, changing the browser's address bar, while a rewrite serves content from a different path but keeps the original URL unchanged in the browser
In Next.js middleware, redirects and rewrites are two distinct ways to modify request handling, with fundamentally different effects on the user experience. A redirect tells the browser to make a new request to a different URL, which updates what the user sees in their address bar. A rewrite, on the other hand, keeps the original URL in the browser but serves content from a different route on the server. Understanding this distinction is crucial for implementing authentication flows, A/B testing, localization, and maintaining SEO-friendly URLs while changing underlying content.
A redirect is an HTTP response that tells the client (browser) to make a new request to a different URL. When you use NextResponse.redirect() in middleware, Next.js sends an HTTP status code (typically 307 for temporary redirects or 308 for permanent) along with a Location header pointing to the new URL . The browser then automatically requests that new URL, and the address bar updates to reflect it . Redirects are ideal for authentication flows (sending unauthenticated users to a login page), handling moved content, or enforcing canonical URLs . A GitHub discussion confirms that when a redirect works correctly, the browser URL should change—if it doesn't, you might be accidentally using a rewrite instead .
A rewrite is a server-side mapping that serves content from a different URL while keeping the original URL in the browser's address bar . When you use NextResponse.rewrite() in middleware, the server internally maps the request to a different destination path, but the client never knows this happened—the URL stays exactly as the user requested . This is powerful for A/B testing (showing different page variants at the same URL), hiding internal API endpoints, implementing content negotiation, or maintaining backward compatibility when your routing structure changes . The Sanity documentation provides an excellent example: using rewrites to serve either HTML or markdown from the same /docs/guides/setup URL based on the Accept header, without changing what the user sees in their browser .
Browser URL: Redirect changes the URL; rewrite preserves the original URL .
HTTP status: Redirect sends 3xx status codes; rewrite returns 200 with content from another route .
Client awareness: Redirect involves a second round-trip to the new URL; rewrite is invisible to the client .
Use cases: Redirect for auth flows, moved pages; rewrite for A/B testing, content negotiation, masking internal paths .
A common mistake developers encounter is expecting a redirect but accidentally implementing a rewrite, or vice versa. One GitHub discussion highlights a user who expected their middleware to redirect unauthenticated users from /profile to /sign-in, but the URL remained /profile—a classic symptom of using rewrite instead of redirect . The collaborator's response confirms this: "What you are describing is the behavior of rewrite, rather than redirect." Always verify which function you're using when you need to control browser URL behavior .