How to remember language selection across sessions in Next.js (Pages Router) v16
Store user's explicit language choice
Problem
When a user explicitly selects a language, that choice reflects their preference and should take precedence over automatic detection from browser headers or geolocation. Without persistence, this preference is lost 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 user preferences, creating friction and diminishing trust.
The challenge is twofold: capturing the user's explicit choice at the moment of selection and retrieving that choice on subsequent visits before any automatic detection logic runs. If the stored preference is not checked early in the request lifecycle, the user may be redirected based on browser settings instead of their explicit choice, undermining the value of having made a selection in the first place.
Solution
Store the user's language selection in a persistent cookie when they explicitly choose a language. On future visits to the application root, check for this cookie before falling back to browser-based locale detection. If a valid stored locale is found, redirect the user to that locale's root path, ensuring their preference is honored immediately.
This approach separates explicit user choice from automatic detection. The cookie acts as a durable signal of intent that survives browser restarts and takes priority over transient signals like the Accept-Language header. By checking the cookie server-side during the initial request, the redirect happens before the page renders, providing a seamless experience.
Steps
1. Create a helper to set the locale preference cookie client-side
When a user selects a language, store their choice in a cookie that persists across sessions.
export function setLocalePreference(locale: string) {
const maxAge = 60 * 60 * 24 * 365;
document.cookie = `NEXT_LOCALE=${locale}; path=/; max-age=${maxAge}; SameSite=Lax`;
}
This function writes a cookie named NEXT_LOCALE with the chosen locale, valid for one year. The path=/ ensures it is available across the entire application, and SameSite=Lax provides reasonable CSRF protection while allowing the cookie to be sent on top-level navigation.
2. Call the helper when the user selects a language
Integrate the helper into your language switcher component so the preference is saved immediately upon selection.
import { useRouter } from "next/router";
import { setLocalePreference } from "@/lib/locale";
export default function LanguageSwitcher() {
const router = useRouter();
const { locales, locale: currentLocale } = router;
const handleLocaleChange = (newLocale: string) => {
setLocalePreference(newLocale);
router.push(router.pathname, router.asPath, { locale: newLocale });
};
return (
<select
value={currentLocale}
onChange={(e) => handleLocaleChange(e.target.value)}
>
{locales?.map((loc) => (
<option key={loc} value={loc}>
{loc.toUpperCase()}
</option>
))}
</select>
);
}
When the user changes the selection, the cookie is set and the router navigates to the same page in the new locale. The cookie will be available on all subsequent requests.
3. Check the stored preference on the root page
In the root page's getServerSideProps, read the cookie and redirect to the stored locale if it exists and is valid.
import { GetServerSideProps } from "next";
export const getServerSideProps: GetServerSideProps = async (context) => {
const storedLocale = context.req.cookies.NEXT_LOCALE;
const { locales, defaultLocale } = context;
if (
storedLocale &&
locales?.includes(storedLocale) &&
storedLocale !== defaultLocale
) {
return {
redirect: {
destination: `/${storedLocale}`,
permanent: false,
},
};
}
return {
redirect: {
destination: `/${defaultLocale}`,
permanent: false,
},
};
};
export default function RootPage() {
return null;
}
This checks if the NEXT_LOCALE cookie exists and contains a valid locale from the application's configured list. If the stored locale is not the default, the user is redirected to that locale's root. Otherwise, they are redirected to the default locale. The redirect happens server-side before rendering, ensuring the user lands on the correct locale immediately.
4. Configure locale routing in Next.js
Ensure your next.config.js defines the supported locales so the redirect logic can validate the stored preference.
module.exports = {
i18n: {
locales: ["en", "fr", "de", "es"],
defaultLocale: "en",
},
};
This configuration enables Next.js's built-in i18n routing and makes the locales and defaultLocale available in getServerSideProps. The root page logic uses these values to validate the stored cookie and construct the correct redirect destination.