How to remember language selection across sessions in React Router v7
Store user's explicit language choice
Problem
When a user explicitly selects a language, that choice reflects their preference and should override any automatic detection. Without persistence, this selection vanishes when the browser closes or the session ends. On the next visit, the application starts fresh, forcing the user to select their language again. This repetition signals that the application does not respect their preferences, creating friction and diminishing trust.
Solution
Store the user's language choice in a persistent location such as a cookie when they make a selection. On subsequent visits, check for this stored preference before falling back to browser headers or other detection methods. If a valid stored language is found, redirect the user to that language's route automatically. This ensures their explicit choice takes precedence and persists across sessions.
Steps
1. Create a cookie for storing the language preference
Define a cookie that will hold the user's selected language with a long expiration time.
import { createCookie } from "react-router";
export const languagePreference = createCookie("language-preference", {
maxAge: 31536000,
httpOnly: false,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
});
This cookie persists for one year and is accessible to client-side code for reading the preference.
2. Add an action to store the language selection
Create an action that handles language selection form submissions and stores the choice in the cookie.
import { redirect } from "react-router";
import type { Route } from "./+types/root";
import { languagePreference } from "./cookies";
export async function action({ request }: Route.ActionArgs) {
const formData = await request.formData();
const selectedLanguage = formData.get("language");
if (typeof selectedLanguage === "string") {
return redirect(`/${selectedLanguage}`, {
headers: {
"Set-Cookie": await languagePreference.serialize(selectedLanguage),
},
});
}
return redirect("/");
}
When the user submits their language choice, this action stores it in the cookie and redirects them to the appropriate language route.
3. Create a language selection component
Build a form component that allows users to choose their preferred language.
import { Form } from "react-router";
export function LanguageSelector({
currentLanguage,
}: {
currentLanguage: string;
}) {
return (
<Form method="post">
<select
name="language"
defaultValue={currentLanguage}
onChange={(e) => e.currentTarget.form?.requestSubmit()}
>
<option value="en">English</option>
<option value="es">Español</option>
<option value="fr">Français</option>
<option value="de">Deutsch</option>
</select>
</Form>
);
}
This component submits automatically when the user changes the selection, triggering the action that stores the preference.
4. Check for stored preference in the root loader
Add logic to the root route loader that checks for a stored language preference and redirects accordingly.
import { redirect } from "react-router";
import type { Route } from "./+types/root";
import { languagePreference } from "./cookies";
export async function loader({ request }: Route.LoaderArgs) {
const url = new URL(request.url);
const cookieHeader = request.headers.get("Cookie");
const storedLanguage = await languagePreference.parse(cookieHeader);
if (url.pathname === "/" && storedLanguage) {
return redirect(`/${storedLanguage}`);
}
return null;
}
When a user visits the root path, this loader checks for a stored language preference and redirects them to their chosen language route if one exists.
5. Validate the stored language against supported locales
Ensure the stored preference is valid before using it for redirection.
import { redirect } from "react-router";
import type { Route } from "./+types/root";
import { languagePreference } from "./cookies";
const SUPPORTED_LANGUAGES = ["en", "es", "fr", "de"];
export async function loader({ request }: Route.LoaderArgs) {
const url = new URL(request.url);
const cookieHeader = request.headers.get("Cookie");
const storedLanguage = await languagePreference.parse(cookieHeader);
if (
url.pathname === "/" &&
storedLanguage &&
SUPPORTED_LANGUAGES.includes(storedLanguage)
) {
return redirect(`/${storedLanguage}`);
}
return null;
}
This validation prevents redirecting to invalid or unsupported language routes if the cookie value has been tampered with or if supported languages have changed since the preference was stored.