Welcome back to the TanStack Router series, today going double digits with
The problem in one sentence
You sign in and get redirected to the homepage, losing the page and search parameters you had before.
Approach 1: explicit redirect via search param
This is the straightforward and reliable solution. We explicitly pass a redirectTo param everywhere the user can navigate to the sign in page and we use it to go back after signing in.
Our Sign In form component will look like this, it defaults to the homepage or redirects to the provided URL after a successful sign in.
export const SignInForm = ({ redirectTo = '/' }: { redirectTo?: string }) => {
const navigate = useNavigate();
const signIn = () => {
// Your sign in logic here
navigate({ to: redirectTo });
};
return (
<form onSubmit={signIn}>
{/* form fields */}
<Button type="submit">Sign In</Button>
</form>
);
};
But where does redirectTo come from? Let's add it as a search parameter to the sign in route.
import { createFileRoute } from '@tanstack/react-router';
import { SignInForm } from 'src/components/auth/sign-in-form';
import { Layout } from 'src/components/layout';
import { z } from 'zod';
export const Route = createFileRoute('/sign-in')({
component: RouteComponent,
validateSearch: z.object({
redirectTo: z.string().optional().catch('/'),
}),
// ...
});
function RouteComponent() {
const { redirectTo } = Route.useSearch();
return (
<Layout>
{/* ... */}
<SignInForm redirectTo={redirectTo} />
{/* ... */}
</Layout>
);
}
What is that validateSearch doing? It uses Zod to parse the search parameters and extract redirectTo, defaulting it to / if not provided. Then in the component we read it with Route.useSearch() and pass it down to the SignInForm.
If you want to learn more about handling search parameters with TanStack Router, check out my previous article:
With this, after a successful sign in you will land back on the original page including all the search parameters.
Tradeoff of the explicit approach
You must remember to add the redirectTo param in every link to the sign in page. Not a big issue though, and you can wrap it in a helper or custom hook like a "go to sign in" utility, but it is still one more thing to do.
Approach 2: capture previous location automatically
I also experimented with a small hook that tracks the previous location without passing any search parameters to the sign in page. I'm not entirely sure how stable and reliable this is, but it worked well in my tests so I thought it was worth sharing:
function usePreviousLocation() {
const router = useRouter();
const [previousLocation, setPreviousLocation] = useState<string>('/');
useEffect(() => {
return router.subscribe('onResolved', ({ fromLocation }) => {
setPreviousLocation(fromLocation?.href ?? '/');
});
}, []);
return previousLocation;
}
This hook subscribes to the router's navigation events and captures the "from" location after each navigation. It stores it in state so it can be used later.
With this alone you can forget about everything we already said, you'll no longer need to pass redirectTo in each navigation or handle search params. Just use the hook in your sign in form:
export const SignInForm = () => {
const navigate = useNavigate();
const previousLocation = usePreviousLocation();
const signIn = () => {
// Your sign in logic here
navigate({ to: previousLocation });
};
return (
<form onSubmit={signIn}>
{/* form fields */}
<Button type="submit">Sign In</Button>
</form>
);
};
Combine both for a robust UX
The two approaches might also work well together. You can prefer the explicit redirectTo when provided, and fall back to the previously captured location when it is not. This covers both deliberate redirects and default behavior without extra effort.
export const SignInForm = ({ redirectTo }: { redirectTo?: string }) => {
const navigate = useNavigate();
const previousLocation = usePreviousLocation();
const signIn = () => {
// Your sign in logic here
navigate({ to: redirectTo ?? previousLocation });
};
return (
<form onSubmit={signIn}>
{/* form fields */}
<Button type="submit">Sign In</Button>
</form>
);
};
Notes
- This article focuses on TanStack Router, but the same idea works in TanStack Start too.
- If you are using authenticated routes or guards, you might also like this related article: , you can find it here:
Do you like my content? You might consider subscribing to my YouTube channel! It means a lot to me ❤️
You can find it here:
SOCIAL SHARE CARD GENERATOR