How to build a language switcher component in Next.js (Pages Router) v16
Switch languages while staying on the same page
Problem
Users expect language switchers to preserve their current location. When someone views a product page in English and switches to Spanish, they want to see that same product page in Spanish, not be redirected to the homepage. Breaking this expectation creates friction and forces users to navigate back to where they were, degrading the experience and potentially causing them to abandon the task entirely.
Many language switcher implementations fail because they treat language selection as simple navigation rather than as a transformation of the current view. Without access to the current page's routing information, a switcher can only link to fixed destinations, losing the user's context in the process.
Solution
Build a language switcher that reads the current route's pathname and query parameters from the router, then generates links for each supported language that preserve this routing information while changing only the locale. By passing the pathname and query to the navigation API along with the target locale, the switcher ensures users stay on the equivalent page in the new language.
Steps
1. Create a language switcher component that reads the current route
The router object provides pathname, asPath, query, locale, and locales properties that contain all the information needed to build locale-aware links.
import { useRouter } from "next/router";
import Link from "next/link";
export default function LanguageSwitcher() {
const router = useRouter();
const { locale, locales, pathname, asPath, query } = router;
return (
<nav>
{locales?.map((loc) => (
<Link key={loc} href={{ pathname, query }} as={asPath} locale={loc}>
{loc.toUpperCase()}
</Link>
))}
</nav>
);
}
The Link component accepts a locale prop to transition to a different locale from the currently active one. Passing pathname and query as an object to href preserves all routing information including dynamic route query values.
2. Style the active locale to provide visual feedback
Highlight the current language so users know which locale they are viewing.
import { useRouter } from "next/router";
import Link from "next/link";
export default function LanguageSwitcher() {
const router = useRouter();
const { locale, locales, pathname, asPath, query } = router;
return (
<nav>
{locales?.map((loc) => {
const isActive = loc === locale;
return (
<Link
key={loc}
href={{ pathname, query }}
as={asPath}
locale={loc}
style={{
fontWeight: isActive ? "bold" : "normal",
textDecoration: isActive ? "none" : "underline",
marginRight: "1rem",
}}
>
{loc.toUpperCase()}
</Link>
);
})}
</nav>
);
}
Comparing each locale to the current locale value identifies the active language and applies distinct styling to differentiate it from available alternatives.
3. Add accessible labels using react-intl
Replace locale codes with human-readable language names for better usability.
import { useRouter } from "next/router";
import { useIntl } from "react-intl";
import Link from "next/link";
const localeNames: Record<string, string> = {
en: "English",
es: "Español",
fr: "Français",
de: "Deutsch",
};
export default function LanguageSwitcher() {
const router = useRouter();
const intl = useIntl();
const { locale, locales, pathname, asPath, query } = router;
return (
<nav
aria-label={intl.formatMessage({
id: "languageSwitcher.label",
defaultMessage: "Select language",
})}
>
{locales?.map((loc) => {
const isActive = loc === locale;
return (
<Link
key={loc}
href={{ pathname, query }}
as={asPath}
locale={loc}
aria-current={isActive ? "true" : undefined}
style={{
fontWeight: isActive ? "bold" : "normal",
textDecoration: isActive ? "none" : "underline",
marginRight: "1rem",
}}
>
{localeNames[loc] || loc}
</Link>
);
})}
</nav>
);
}
The useIntl hook provides access to formatting functions for translating UI labels. The aria-label and aria-current attributes improve accessibility for screen reader users.