The Annoying `redirect()` in Next.js

Contributors:

R. Rahmat Fadli Sadikin

Writer

If you've spent time with Next.js, especially diving into Server Components and Server Actions, you've likely met the redirect() function from next/navigation. It's a useful tool for sending users to new pages, but it comes with a catch that can be incredibly frustrating. It can throw an error so vague it sends you down a rabbit hole of debugging. I spent days dealing with this, questioning my authentication logic, and exploring every alternative I could think of. The solution? Almost frustratingly simple: swapping redirect() for permanentRedirect(). But that just leaves one big question: why on earth does that work?

Redirect vs Permanent Redirect

At first glance, the difference seems simple. redirect is for temporary moves, and permanentRedirect is for when a page has moved for good. But the real distinction lies in the HTTP status codes they send back to the browser, which has a big impact on how your browser behaves.

The redirect() function usually sends a 307 Temporary Redirect. If you're using it inside a Server Action, however, it sends a 303 See Other. On the other hand, permanentRedirect() always sends a 308 Permanent Redirect.

So what's the big deal with these numbers? It's all about what happens to your request method (like GET or POST). With a 307 or 308 redirect, your browser is told to use the same method for the new request. If it was a POST request, it'll make another POST to the new URL. But with a 303 redirect, the browser is explicitly told to switch to a GET request for the new URL. This is a common pattern to prevent things like re-submitting a form if the user refreshes the page after the form submission.

This difference is likely the root of the problem mentioned earlier. If you have logic on the target page that expects a POST request (maybe to process some data), but redirect() from a Server Action is forcing the browser to make a GET request instead and something miss, things will break. By switching to permanentRedirect(), you ensure the request method is preserved, and your POST request lands as a POST, just as you intended.

Conclusion

While I'm still digging into the finer details of why this solution is so effective, I wanted to document my findings. This post serves as a note for my future self and, hopefully, a helpful guide for anyone else who runs into this frustrating issue. If you've been dealing with unexpected redirect behavior in Next.js, I hope this explanation saves you some time and debugging headaches.