How toRedirect to the original URL inside a Remix action
Let's say the user is currently at the URL /:username
, and there's a button to follow that user (like in a Twitter profile page).
function View() {
let { profile } = useRouteData<{ profile: { name: string, id: string }}>()
return (
<div>
<h1>{profile.name}</h1>
<Form method="post" action={`/users/${profile.name}/follow`}>
<input type="hidden" value={profile.id} name="userId" />
<button type="submit">Follow</button>
</Form>
</div>
);
}
After the user submit our form inside our action we will receive the profile.id
and then we need to redirect the user to another URL, but we want the user to redirect back to the URL the user was when it clicked the Follow button.
export let action: ActionFunction = async ({ request }) => {
let body = await parseBody(request);
let session = await getSession(request.headers.get("Cookie"));
await followUser(body.get("userId"), session.get("token")); // imagine this create a new follow in the DB
return redirect("?");
}
So how can we do this? There's a few ways.
Using the parameters
In that example in particular we can get the profile.name
from the params
, so we could do:
export let action: ActionFunction = async ({ request, params }) => {
// the code from the first example, removed for simplicity
return redirect(`/${params.name}`);
}
This will work in this case but it may not if the URL of the action was simply /like
or something without any parameter in the middle
Pass the original URL inside the form
Another option is to send the URL we want to redirect the user to after the action inside the body of the action.
function View() {
// we get the full URL from the loader, in the loader we can just return `request.url`
let { profile, url } = useRouteData<{ profile: { name: string, id: string }, url: string }>()
return (
<div>
<h1>{profile.name}</h1>
<Form method="post" action={`/users/${profile.name}/follow`}>
<input type="hidden" value={url} name="redirectTo" />
<input type="hidden" value={profile.id} name="userId" />
<button type="submit">Follow</button>
</Form>
</div>
);
}
Then in the action:
export let action: ActionFunction = async ({ request, params }) => {
// the code from the first example, removed for simplicity
return redirect(body.get("redirectTo"));
}
And again that will work, we can be sure to always send that redirectTo
value and this can be a nice pattern to follow everytime you need this.
Use the Referer HTTP header
The HTTP header Referer
is a way the browser can send to a server the URL the user was coming from. We can get it from the request
inside the action and use it to get the exact URL the user was before the submit.
export let action: ActionFunction = async ({ request, params }) => {
// the code from the first example, removed for simplicity
return redirect(request.headers.get("Referer"));
}
We can build a simple wrapper called redirectBack
and pass the request and make the wrapper read the header for us. This is what I did for my redirectBack
function in Remix Utils so you can just do:
export let action: ActionFunction = async ({ request, params }) => {
// the code from the first example, removed for simplicity
return redirectBack(request);
}
The main problem with this way of doing the redirect is the Referer header may not be present, e.g if the user defined the CSP header Referrer-Policy: no-referrer
then from that page the browser will never send the Referer to any request. So for those cases we will need a fallback, the redirectBack
function receives it as a second value:
export let action: ActionFunction = async ({ request, params }) => {
// the code from the first example, removed for simplicity
return redirectBack(request, { fallback: "/something" });
}
This way we can define a URL we are ok if the user is redirected to even if it's not the same, an example of this could be if we wanted to preserve search params we can use the Referer header to get them but as a fallback use the URL without search params.
If we combine this with the redirectTo
inside the form body we can have something like this:
export let action: ActionFunction = async ({ request, params }) => {
// the code from the first example, removed for simplicity
return redirectBack(request, { fallback: body.get("redirectTo") });
}