Fixing Third-Party Redirects in JavaScript: Why window.location Beats fetch()
This post covers a subtle but frustrating redirect bug I recently ran into while working on a payment integration.
The Problem
I was generating a payment URL from my backend (using FastAPI), which pointed to a third-party payment gateway.
From the frontend, I made a POST request using fetch()
and then tried to redirect the user like this:
const response = await fetch("/api/create-payment", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ amount: 100 }),
});
const data = await response.json();
window.location.href = data.redirect_url;
The URL looked fine. When I pasted it directly into the browser, it worked. But when the code redirected the user, the third-party page didn't behave correctly.
What's Going On?
This happens because of how fetch()
handles redirects:
- When a redirect occurs (like a 302),
fetch()
will follow it internally. - However, it doesn't cause the browser to actually navigate to the new URL.
- Cookies, headers, and browser context may not carry over properly.
- Some third-party services won't work correctly unless the browser performs the redirect directly.
The Fix
Instead of using fetch()
, I let the browser handle the redirect:
window.location.href = `/api/create-payment?amount=100`;
And on the backend:
from fastapi.responses import RedirectResponse
@app.get("/api/create-payment")
def create_payment(amount: int):
redirect_url = generate_third_party_url(amount)
return RedirectResponse(url=redirect_url, status_code=302)
This worked as expected. The browser performed a clean, full-page redirect with all the right context.
When to Use This
Use this pattern when:
- Redirecting to third-party URLs (payments, OAuth, etc.)
- You want browser-level handling of cookies, headers, or session
- The third-party page doesn't load properly after a
fetch()
-initiated redirect
Takeaway
Avoid using fetch()
when the goal is to navigate the user to a third-party page. Use window.location.href
or window.open()
instead, and let the browser do what it's designed to do.