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.