How to detect user language preferences in TanStack Start v1

Auto-redirect based on browser preferences

Problem

Web browsers send an Accept-Language header with every HTTP request, indicating the user's preferred languages in order of priority. This header provides valuable information about which language the user is most comfortable with, yet many applications ignore it entirely. Instead, they show a default language—typically English—to all visitors, regardless of their actual preferences. Users must then manually search for a language switcher, creating unnecessary friction when the application already has the information needed to make a better initial choice.

This missed opportunity for personalization is particularly frustrating for non-English speakers who have explicitly configured their browser's language preferences. The result is a degraded first impression and additional steps before users can engage with content in their preferred language.

Solution

Intercept requests to the root path and examine the Accept-Language header to determine the user's preferred language. Parse the header to extract the highest-priority language that your application supports. If a match is found, redirect the user to the appropriate localized route. If no supported language is found in the header, redirect to a default language fallback.

This approach respects user preferences automatically while maintaining a clear URL structure where each language has its own path prefix. The redirect happens server-side before the page renders, ensuring users land directly on content in their preferred language without seeing a flash of the wrong language.

Steps

1. Create a helper to parse the Accept-Language header

The Accept-Language header contains language codes with optional quality values that indicate preference order. Build a parser that extracts these languages, sorts them by priority, and finds the first match from your supported languages list.

export function parseAcceptLanguage(
  header: string | null,
  supportedLocales: string[],
): string | null {
  if (!header) {
    return null;
  }

  const languages = header
    .split(",")
    .map((lang) => {
      const [code, qValue] = lang.trim().split(";q=");
      const quality = qValue ? parseFloat(qValue) : 1.0;
      return { code: code.toLowerCase(), quality };
    })
    .sort((a, b) => b.quality - a.quality);

  for (const { code } of languages) {
    const exactMatch = supportedLocales.find(
      (locale) => locale.toLowerCase() === code,
    );
    if (exactMatch) {
      return exactMatch;
    }

    const baseCode = code.split("-")[0];
    const baseMatch = supportedLocales.find((locale) =>
      locale.toLowerCase().startsWith(baseCode),
    );
    if (baseMatch) {
      return baseMatch;
    }
  }

  return null;
}

This function splits the header by commas, extracts quality values, sorts by preference, and checks for both exact matches and base language matches against your supported locales.

2. Define your supported locales

Create a configuration that lists all languages your application supports and specifies which one to use as the default fallback.

export const SUPPORTED_LOCALES = ["en", "fr", "de", "es", "ja"];
export const DEFAULT_LOCALE = "en";

These constants centralize your language configuration and make it easy to add or remove supported languages as your application grows.

3. Create a server route handler for the root path

Add a server-side GET handler to your index route that reads the Accept-Language header, determines the best locale, and redirects the user to the appropriate language-specific path.

import { createFileRoute, redirect } from "@tanstack/react-router";
import { getRequestHeaders } from "@tanstack/react-start/server";

export const Route = createFileRoute("/")({
  server: {
    handlers: {
      GET: async () => {
        const headers = getRequestHeaders();
        const acceptLanguage = headers.get("accept-language");

        const preferredLocale = parseAcceptLanguage(
          acceptLanguage,
          SUPPORTED_LOCALES,
        );

        const targetLocale = preferredLocale || DEFAULT_LOCALE;

        throw redirect({
          to: `/${targetLocale}`,
          statusCode: 302,
        });
      },
    },
  },
});

This handler runs on the server when a user visits the root path, examines their language preferences, and immediately redirects them to the best matching locale route before any client-side code executes.

4. Import the helper function

Ensure the parse helper is available in your route file by importing it at the top alongside your locale configuration.

import { createFileRoute, redirect } from "@tanstack/react-router";
import { getRequestHeaders } from "@tanstack/react-start/server";
import {
  parseAcceptLanguage,
  SUPPORTED_LOCALES,
  DEFAULT_LOCALE,
} from "../lib/locale";

Place the parseAcceptLanguage function and locale constants in a shared module so they can be reused across your application if needed.