How to maintain locale in navigation links in React Router v7

Maintain locale across internal navigation

Problem

When locale information is encoded in the URL path, every navigation link must preserve that locale to maintain a consistent user experience. If a user browses the French version of your site and clicks a link to /about, they expect to stay in French and navigate to /fr/about. Without locale-aware links, users are dropped into the default language mid-session, breaking their browsing context and forcing them to manually switch languages again. This creates friction and undermines the localized experience.

Hard-coding the locale prefix into every link is error-prone and makes the codebase brittle. As users navigate through the application, the active locale can change, and manually updating hundreds of links becomes unsustainable.

Solution

Create a custom Link component that automatically reads the current locale from the URL and prepends it to all internal navigation paths. By wrapping React Router's Link component, you centralize locale handling in one place. The wrapper extracts the locale parameter from the current route and ensures every destination path includes it, so navigation preserves the user's language choice without manual intervention.

This approach keeps link definitions clean and locale-agnostic throughout your application while guaranteeing that the locale context travels with every click.

Steps

Build a custom component that uses useParams to extract the current locale from the URL and wraps React Router's Link component to prepend the locale to the destination path.

import { Link, useParams } from "react-router";
import type { LinkProps } from "react-router";

export function LocaleLink({ to, ...props }: LinkProps) {
  const { locale } = useParams<{ locale: string }>();

  const localizedTo =
    typeof to === "string"
      ? `/${locale}${to.startsWith("/") ? to : `/${to}`}`
      : {
          ...to,
          pathname: `/${locale}${to.pathname?.startsWith("/") ? to.pathname : `/${to.pathname}`}`,
        };

  return <Link to={localizedTo} {...props} />;
}

This component reads the locale parameter from the current route and automatically prefixes it to any path you pass to the to prop, handling both string and object forms.

Replace standard Link components with LocaleLink wherever you need locale-preserving navigation.

import { LocaleLink } from "./LocaleLink";

export function Navigation() {
  return (
    <nav>
      <LocaleLink to="/">Home</LocaleLink>
      <LocaleLink to="/about">About</LocaleLink>
      <LocaleLink to="/products">Products</LocaleLink>
    </nav>
  );
}

When a user on /fr/products clicks the About link, they navigate to /fr/about. The locale prefix is added automatically without cluttering the link definition.

Extend the wrapper to detect when a path already includes the locale or points to an external URL, avoiding double-prefixing or breaking external navigation.

import { Link, useParams } from "react-router";
import type { LinkProps } from "react-router";

export function LocaleLink({ to, ...props }: LinkProps) {
  const { locale } = useParams<{ locale: string }>();

  if (!locale) {
    return <Link to={to} {...props} />;
  }

  const isExternal =
    typeof to === "string" &&
    (to.startsWith("http://") || to.startsWith("https://"));
  const alreadyLocalized =
    typeof to === "string" && to.startsWith(`/${locale}/`);

  if (isExternal || alreadyLocalized) {
    return <Link to={to} {...props} />;
  }

  const localizedTo =
    typeof to === "string"
      ? `/${locale}${to.startsWith("/") ? to : `/${to}`}`
      : {
          ...to,
          pathname: `/${locale}${to.pathname?.startsWith("/") ? to.pathname : `/${to.pathname}`}`,
        };

  return <Link to={localizedTo} {...props} />;
}

This guards against double-prefixing if a path already starts with the locale and passes through external URLs unchanged, ensuring the component works reliably in all navigation scenarios.