How to maintain locale in navigation links in TanStack Start v1

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 a site at /fr/products, clicking a link to /about drops them into the default language, breaking their session context. Hard-coding locale parameters into every link is repetitive and error-prone, especially as the application grows and more links are added throughout components.

Without a systematic approach to locale-aware navigation, developers face a choice between manually threading locale through every Link component or accepting that users will be unexpectedly switched to the default language mid-session. Both outcomes degrade the user experience and increase maintenance burden.

Solution

Create a wrapper component around the framework's Link that automatically reads the current locale from the URL and injects it into the params prop of every navigation target. By centralizing this logic in a single reusable component, all internal links inherit locale-aware behavior without requiring developers to manually pass locale parameters at every call site.

This approach works by reading the active locale from route parameters using the router's hooks, then merging that locale into the destination link's parameters. The wrapper preserves all other Link functionality while ensuring locale continuity across navigation.

Steps

Build a component that reads the current locale and automatically includes it in navigation parameters.

import { Link, LinkProps, useParams } from "@tanstack/react-router";

type LocaleLinkProps = LinkProps & {
  to: string;
};

export function LocaleLink(props: LocaleLinkProps) {
  const params = useParams({ strict: false });
  const currentLocale = params.locale;

  const enhancedParams = {
    ...props.params,
    ...(currentLocale && { locale: currentLocale }),
  };

  return <Link {...props} params={enhancedParams} />;
}

This component uses useParams with strict: false to access parameters from any route in the application, extracts the current locale, and merges it into the params prop passed to the underlying Link. The spread operator ensures any explicitly provided params take precedence.

Replace standard Link components with LocaleLink throughout your application.

import { createFileRoute } from "@tanstack/react-router";
import { LocaleLink } from "../components/LocaleLink";

export const Route = createFileRoute("/{-$locale}/products")({
  component: ProductsPage,
});

function ProductsPage() {
  return (
    <div>
      <h1>Products</h1>
      <nav>
        <LocaleLink to="/{-$locale}/about">About</LocaleLink>
        <LocaleLink to="/{-$locale}/contact">Contact</LocaleLink>
        <LocaleLink to="/{-$locale}/products/$id" params={{ id: "123" }}>
          Product 123
        </LocaleLink>
      </nav>
    </div>
  );
}

When a user visits /fr/products, all LocaleLink components automatically resolve to /fr/about, /fr/contact, and /fr/products/123. The locale parameter is preserved without manual intervention at each link site.

3. Handle locale switching explicitly when needed

For language switcher components, bypass the automatic locale injection by using the standard Link directly.

import { Link, useParams } from "@tanstack/react-router";

export function LanguageSwitcher() {
  const params = useParams({ strict: false });
  const currentPath = window.location.pathname.replace(/^\/(en|fr|es)/, "");

  return (
    <div>
      <Link to={`/{-$locale}${currentPath}`} params={{ locale: "en" }}>
        English
      </Link>
      <Link to={`/{-$locale}${currentPath}`} params={{ locale: "fr" }}>
        Français
      </Link>
      <Link to={`/{-$locale}${currentPath}`} params={{ locale: "es" }}>
        Español
      </Link>
    </div>
  );
}

Language switchers require explicit control over the locale parameter, so they use the standard Link component and explicitly set the locale param. This allows users to change languages while staying on the same logical page.