I Lost 30% of Sales Because Users Were Redirected to Home
1. Why Aren't They Buying?
Usually, when you launch a new e-commerce site, you expect sales to tick up.
But in my case, something was remarkably wrong. I noticed a strange pattern in my analytics data.
Clicks on "Add to Cart" → "Checkout" were surprisingly high, but the actual Conversion Rate (Payment Completion) was extremely low.
The churn rate at the checkout step was nearly 70%.
"Wait, these people decided to buy and clicked the button. Why are they all running away at the very last second?"
Frustrated, I installed Hotjar (a user recording tool) to spy on my users' behavior.
And watching the playback, I almost punched my monitor.
- User finds a product and clicks the [Buy Now] button.
- They are not logged in, so they are redirected to the [Login Page]. (This is normal).
- User types ID/Password and Login is Successful.
- The system redirects the user to... [Home Page] (?!).
- User pauses. Mouse cursor wanders. "Huh? What was I buying? Do I have to search for it again? Ugh, forget it."
-
Close Tab (Churn).
My system was successfully logging them in, but instead of sending them back to the Checkout Page where they wanted to be, it was stupidly sending everyone to the Home Page (/).
This tiny, trivial detail was eating away 30% of my revenue. It wasn't a technical bug (no errors were thrown), but it was a UX Disaster.
2. The Solution: "Where Did You Come From?" (Query String Magic)
To solve this, the Login Page needs to remember "Where the user came from".
You could use global state management libraries like Redux or Zustand, or even localStorage. But the most robust, stateless, and shareable way is to use the URL Query String.
Standard parameter names include redirect, callbackUrl, returnTo, or simply next.
Step 1: Attach a 'Note' When Redirecting to Login
Imagine a user tries to access /checkout (a protected route) but is logged out.
Don't just kick them to /login. Attach a note on their back saying "I came from /checkout".
/* Next.js Middleware or Protected Route Logic */
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const currentUser = request.cookies.get('currentUser');
// 1. If user is not logged in and tries to access a protected page
if (!currentUser && request.nextUrl.pathname.startsWith('/checkout')) {
const loginUrl = new URL('/login', request.url);
// 2. Attach current path to 'redirect' parameter
// Becomes: /login?redirect=/checkout
loginUrl.searchParams.set('redirect', request.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
}
}
Now, the URL bar in the user's browser looks like this: https://myshop.com/login?redirect=/checkout.
Step 2: Read the 'Note' After Login
Now, your Login Component shouldn't just blindly router.push('/') upon success.
It must check the redirect value in the URL and obey it.
/* Login Page Component (Client Side) */
import { useRouter, useSearchParams } from 'next/navigation';
import { useState } from 'react';
export default function LoginPage() {
const router = useRouter();
const searchParams = useSearchParams();
// 3. Read 'redirect' value. Default to '/' if missing.
const redirectUrl = searchParams.get('redirect') || '/';
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
const result = await loginApi(email, password);
if (result.success) {
// 4. Send them back to where they wanted to go!
router.push(redirectUrl);
}
};
// ... (JSX for form)
}
Deploying this tiny 10-line change had an immediate effect. The conversion rate at checkout doubled.
I learned strictly that Keeping the User Flow unbroken is not just about "usability"—it's directly tied to revenue.
3. But What If Hackers Exploit This? (Open Redirect)
I was packing my bag, happy with the sales recovery, when my friend from the Security Team grabbed my shoulder.
"Hey, smart move. But you just introduced an Open Redirect vulnerability. You're in trouble."
"Open Redirect? What's that?"
Imagine a hacker crafting a phishing link and sending it to millions of users via email:
[Event] You won a $100 Gift Card! Login to claim:
https://my-shop.com/login?redirect=https://hacker-site.com
- The user sees the
my-shop.com domain at the beginning. "Oh, it's the official shop. Safe."
- They click and log in with their real credentials.
- Login Success! My code reads the
redirect param (https://hacker-site.com) and dutifully sends the user there.
- The hacker's site is a perfect clone of Google or your shop's login page.
- User thinks, "Oh? Did I get logged out?" and types their password again.
-
Account Stolen.
Scary, right?
My friendly, helpful redirect feature can become a Superhighway for hackers to launder their malicious links.
Tech giants like Google and Facebook suffered from this in their early days too.
4. Iron Defense: "Internal Traffic Only"
The solution is simple: Validation.
We must verify if the redirect address is an Internal Path. If it points to an external domain, we forcefully redirect to Home.
Creating a Safe Redirect Function
const safeRedirect = (path: string): string => {
// 1. Defense: Block absolute URLs starting with http/https
// This blocks 99% of external redirects.
if (path.startsWith('http:') || path.startsWith('https:')) {
console.warn(`[Security] Blocked external redirect attempt: ${path}`);
return '/';
}
// 2. Defense: Block if it doesn't start with "/" (prevent relative path attacks)
if (!path.startsWith('/')) {
return '/';
}
// 3. Defense: Block "//" (Protocol Relative URL Attack)
// Example: //google.com is interpreted by browsers as http://google.com or https://google.com
if (path.startsWith('//')) {
return '/';
}
// 4. Defense: Block Control Characters (CRLF injection)
if (/[\\x00-\\x1F\\x7F]/.test(path)) {
return '/';
}
return path; // Verified Safe!
};
// Usage
router.push(safeRedirect(redirectUrl));
Now, even if a hacker inputs ?redirect=https://hacker.com, my code says, "Wait, this is an external domain!" and sends the user to /.
The user is safe, and the hacker is defeated.
Advanced: Whitelisting
What if your company owns multiple domains (store.com, blog.store.com) and you need to allow external cross-domain redirects?
In that case, use a Whitelist.
const ALLOWED_DOMAINS = ['myshop.com', 'blog.myshop.com'];
const isSafeDomain = (url) => {
try {
const parsedUrl = new URL(url);
return ALLOWED_DOMAINS.includes(parsedUrl.hostname);
} catch (e) {
return false; // Block if URL parsing fails
}
};
Security principles are simple: "Allow only what is known. Block everything else."
5. UX Details: Tips for Perfection
The core logic is done, but let's polish the UX.
1) What about POST requests?
If a user clicked "add to cart" (a POST action) and got redirected to login?
When they return, the cart will be empty because the POST action was never completed or lost.
For this, you need a more complex mechanism: save the "intended action" (product ID, quantity) in localStorage or Cookie before redirecting to login, and then execute it automatically after login. (This is a topic for another post).
2) Hiding the Ugly URL
?redirect=/very/long/path/with/lots/of/params can look ugly.
Some sites base64-encode this value, or store it in sessionStorage instead of the URL.
However, I prefer the Query String method because it's stateless and works even if the user shares the login link with a friend (though they shouldn't).
3) Preventing Redirect Loops
What if the redirect value is... /login?
The user logs in, gets redirected to /login, sees the page again... infinite loop.
Add a one-liner: if (path === '/login') return '/';
6. Conclusion: Fine Line Between Helpful and Harmful
Login redirect is a mandatory feature for any SaaS, E-commerce, or Admin panel.
But implementing it incorrectly can either kill your revenue (Bad UX) or expose users to phishing (Bad Security).
Summary:
- Always send users back. It's the highest ROI feature you can build.
- Use Query Strings (
?redirect=...). It's the standard for a reason.
- Sanitize the Input. Never trust the URL. Use a
safeRedirect function.
Is your login page polite? Or is it leaving your users lost in the lobby?
Open your app, try to buy something while logged out, and see where you end up.