---
title: Route by browser language
description: Send visitors from the default-locale URL to the locale that matches their browser language.
---

`routeByBrowserLanguage()` is an Astro integration that uses Blume's native i18n routing. The default locale is served without a prefix, and the integration redirects a default-locale page such as `/guide` to `/en/guide` when the visitor's browser prefers English. When the visitor prefers the default locale, the page stays at `/guide`.

## What it does [#what-it-does]

- Uses the locale saved in `localStorage` under `blume-locale`, which Blume's language switcher writes, when that locale serves the page. This takes priority over the browser's language preferences.
- Checks the browser's language preferences (`navigator.languages`) in order and redirects to the first matching locale. `ja-JP` matches `ja`.
- If neither the saved locale nor a browser language matches, chooses Blume's fallback locale, then `en`, then the first locale that serves the page.
- Redirects default-locale-prefixed aliases such as `/ja/guide` to the canonical `/guide` URL. The browser-language routing then runs on the canonical page.
- Leaves URLs for other locales, such as `/en/guide`, as they are.
- Preserves query strings and URL fragments.

## How it works [#how-it-works]

The integration adds two things to a site configured with Blume's i18n:

1. A middleware inserts a small inline script into the `<head>` of every default-locale page. The script runs before the page paints and redirects to the localized URL when another locale serves the page.
2. A `/<defaultLocale>/[...slug]` route generates redirect pages at build time. These pages send prefixed default-locale URLs to their canonical, unprefixed URL.

The integration does not change Astro's or Blume's i18n settings. Set `i18n.defaultLocale` to one of the codes in `i18n.locales`, and keep the default locale's content at the content root as Blume expects.

## Setup [#setup]

```package-install
npm i @tai-kun/blume-integrations
```

Add the integration to `integrations` in `blume.config.ts` and configure Blume's i18n with a real default locale.

```ts blume.config.ts
import { routeByBrowserLanguage } from "@tai-kun/blume-integrations";
import { defineConfig } from "blume";

export default defineConfig({
  integrations: [routeByBrowserLanguage()],

  i18n: {
    defaultLocale: "ja",
    locales: [
      { code: "ja", label: "日本語" },
      { code: "en", label: "English" },
    ],
  },
});
```

`defaultLocale` must be the code of one of the entries in `locales`. There is no placeholder locale to add.

:::note
If Blume's i18n is not configured, this integration does not inject redirects or create redirect pages.
:::

## Content layout [#content-layout]

Put the default locale's pages at the content root. Every other locale gets a top-level folder named by its code.

```txt
content/
  index.mdx
  guide.mdx
  en/
    index.mdx
    guide.mdx
```

With `defaultLocale: "ja"`, Blume serves `content/index.mdx` at `/` and `content/en/index.mdx` at `/en`. Requests to `/` and `/guide` show the Japanese pages when Japanese is preferred, and redirect to `/en` and `/en/guide` when English is preferred.

## Verify [#verify]

Open a default-locale URL such as `/guide` on the built site. The setup works if it redirects to a page in the locale that matches the browser's language preferences, or to the language last chosen in Blume's language switcher. Changing the browser's language preferences changes which locale is selected when no choice has been saved.

You can also open `/ja/guide`. It redirects to `/guide`, the canonical URL for the default locale.

## How destinations are chosen [#matching]

Only locales that serve the page are candidates. Candidates follow the order of `i18n.locales`, and the default locale is always a candidate. On the client, the destination is chosen in this order.

1. If `localStorage` stores a locale under `blume-locale` and that locale serves the page, choose it. Blume's language switcher saves the language picked there under this key.
2. Otherwise, check `navigator.languages` from the start for a language tag that either exactly matches a locale code or continues with `-` after the locale code. For example, `ja-JP` matches `ja`.
3. If there is no match, choose Blume's `fallbackLocale`, but only if it serves the page.
4. If `fallbackLocale` is not available, choose `en`, but only if it serves the page.
5. If none is available, choose the first locale in the candidate list.

| Browser language | Locales serving the page | Destination                             |
| ---------------- | ------------------------ | --------------------------------------- |
| `ja-JP`          | `ja`, `en`               | `/guide`                                |
| `en-US`          | `ja`, `en`               | `/en/guide`                             |
| `fr-FR`          | `ja`, `en`               | `/en/guide` if `fallbackLocale` is `en` |
| `fr-FR`          | `ja` only                | `/guide`                                |

Because redirects use `window.location.replace()`, the original URL is not left in browser history. The integration does not write to storage; only Blume's language switcher saves the chosen locale. If storage is blocked, the saved locale cannot be read and the browser's language preferences decide the destination.

## What gets redirected [#scope]

The browser-language redirect runs only on default-locale pages. Pages hidden in the sidebar (`sidebar.hidden: true` or `hidden: true`) are not redirected. URLs for other locales are never redirected, so an explicit `/en/guide` stays in English.

Default-locale-prefixed aliases are created for every visible default-locale page. Versioned URLs are supported too: `/ja/v1/guide` redirects to `/v1/guide`, and browser-language routing applies there as well. The deployment base path and Blume's `basePath` are included in destinations.

## Without JavaScript [#without-javascript]

Default-locale pages are real, prerendered pages. When JavaScript cannot run, visitors simply stay on the default-locale content instead of being routed by language.

The prefix alias pages (`/ja/…`) are redirect-only pages. In case JavaScript cannot run, they list the canonical default-locale page as a link. AI agents and crawlers can follow that link.

## API [#api]

| Export                   | Type                     | Description              |
| ------------------------ | ------------------------ | ------------------------ |
| `routeByBrowserLanguage` | `() => AstroIntegration` | Creates the integration. |

## Caveats [#caveats]

- Because the browser-language redirect runs in client-side JavaScript, the default-locale page may appear briefly on first load before the redirect. The alias pages hide their contents for the first second to avoid flicker.
- The output is static HTML. The language is not determined on the server.
- The integration relies on Blume's i18n data and assumes `defaultLocale` is one of the configured `locales`.
- Do not give the default locale its own content folder. Pages at the content root belong to the default locale; other locales use their code as a folder name.
