---
title: "TanStack Start"
description: "Connect the Vite compiler to request-aware locale resolution and matching hydration state."
canonical_url: "https://docs.linguana.dev/docs/getting-started/tanstack-start"
markdown_url: "https://docs.linguana.dev/docs/getting-started/tanstack-start.md"
x_farming_labs_generated_preamble: true
agent:
  task: "Integrate Linguana compiler and request locale hydration into TanStack Start."
  outcome: "The selected locale renders consistently on server and client with embedded source fallback."
  appliesTo:
    framework:
      - "TanStack Start"
    package:
      - "@linguanahq/vite"
      - "@linguanahq/react"
  prerequisites:
    - "The app uses React 19 and a supported Vite version."
    - "An embedded source catalog is available."
  files:
    - "vite.config.ts"
    - "src/locale-bootstrap.ts"
    - "The route that owns LocaleProvider."
  commands:
    - "bun run build"
  verification:
    - "A request with Accept-Language: fr hydrates without a text mismatch when French exists."
  rollback:
    - "Return source-only hydration state and remove remote runtime options."
  failureModes:
    - symptom: "React reports a hydration mismatch."
      resolution: "Pass the exact server-created LocaleHydrationState to LocaleProvider."
---

# TanStack Start
URL: /docs/getting-started/tanstack-start
LLM index: /llms.txt
Description: Connect the Vite compiler to request-aware locale resolution and matching hydration state.
Related: /docs/build-with-code/server-rendering, /docs/build-with-code/show-translated-content

<!-- farming-labs:agent-contract:start -->
## Agent Contract

Task: Integrate Linguana compiler and request locale hydration into TanStack Start.
Outcome: The selected locale renders consistently on server and client with embedded source fallback.

### Applies To

- Framework: `TanStack Start`
- Package: `@linguanahq/vite`, `@linguanahq/react`

### Prerequisites

- The app uses React 19 and a supported Vite version.
- An embedded source catalog is available.

### Files

- `vite.config.ts`
- `src/locale-bootstrap.ts`
- `The route that owns LocaleProvider.`

### Commands

- `bun run build`

### Verification

- A request with Accept-Language: fr hydrates without a text mismatch when French exists.

### Rollback

- Return source-only hydration state and remove remote runtime options.

### Failure Modes

- React reports a hydration mismatch. — Recovery: Pass the exact server-created LocaleHydrationState to LocaleProvider.
<!-- farming-labs:agent-contract:end -->

# Add Linguana to TanStack Start

For the public `0.1.0` SDK, configure Vite scripts with `--configLoader runner` as shown in [Install Linguana](/docs/getting-started/install). This lets Node load the packages’ TypeScript source.

This guide shows how to keep the selected language consistent between the server and the browser.

Install the React packages and configure the config loader using [Installation](/docs/getting-started/install). Keep your existing TanStack Start and deployment plugins; the Nitro example below applies to apps that already use Nitro.

## 1. Configure Vite

```ts title="vite.config.ts"
import { linguana } from "@linguanahq/vite";
import { react as linguanaReact } from "@linguanahq/vite/react";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import viteReact from "@vitejs/plugin-react";
import { nitro } from "nitro/vite";
import { defineConfig } from "vite";

const token = process.env.LINGUANA_PROJECT_TOKEN;

export default defineConfig({
  ssr: { noExternal: [/^@linguanahq\//] },
  plugins: [
    linguana({
      adapter: linguanaReact(),
      projectId: "your-project-id",
      environment: "development",
      sourceLocale: "en",
      apiUrl: process.env.LINGUANA_API_URL,
      token,
      upload: process.env.LINGUANA_UPLOAD === "true",
      translation: token && process.env.LINGUANA_TRANSLATE !== "false"
        ? {
            enabled: true,
            maxChargeMicros: 100_000,
            waitTimeoutMs: 300_000,
            publish: "development",
          }
        : undefined,
    }),
    tanstackStart(),
    nitro(),
    viteReact(),
  ],
});
```

The `ssr.noExternal` setting lets Vite transform the published SDK TypeScript and TSX for server rendering. Keep the project token in the build environment. For values stored in `.env.local`, use `loadEnv` as shown in [Connect hosted translations](/docs/getting-started/connect-your-app).

## 2. Resolve the language for each request

```ts title="src/locale-bootstrap.ts"
import type { Catalog } from "@linguanahq/catalog";
import { createHydrationState, loadCatalog } from "@linguanahq/react";
import { resolveRequestLocale } from "@linguanahq/react/ssr";
import { createServerFn } from "@tanstack/react-start";
import { getRequest } from "@tanstack/react-start/server";
import { embeddedEnglish } from "./catalogs/en";

const projectId = "your-project-id";
const environment = "development";
const supportedLocales = ["en", "fr"] as const;

export const getLocaleBootstrap = createServerFn({ method: "GET" }).handler(
  async () => {
    const locale = resolveRequestLocale({
      request: getRequest(),
      supportedLocales,
      defaultLocale: "en",
    });

    let remote: Catalog | undefined;
    if (locale !== "en") {
      try {
        remote = await loadCatalog({
          endpoint: process.env.LINGUANA_API_URL!,
          projectId,
          environment,
          locale,
          publishableKey: process.env.LINGUANA_PUBLISHABLE_KEY!,
        });
      } catch {
        // Return deterministic source state when the target is unavailable.
      }
    }

    return createHydrationState(
      remote ? locale : "en",
      remote ? [embeddedEnglish, remote] : [embeddedEnglish],
    );
  },
);
```

The server uses `LINGUANA_API_URL`. The browser may use `VITE_LINGUANA_API_URL` for later locale changes when container and public origins differ.

## 3. Pass the same state to the browser

Call `getLocaleBootstrap()` in the route that owns your application shell, then pass its return value unchanged:

```tsx title="src/routes/app.tsx"
import { createFileRoute, Outlet } from "@tanstack/react-router";
import { LocaleProvider } from "@linguanahq/react";
import { getLocaleBootstrap } from "../locale-bootstrap";

export const Route = createFileRoute("/app")({
  loader: () => getLocaleBootstrap(),
  component: ApplicationRoute,
});

const availableLocales = [
  { code: "en", displayName: "English", nativeName: "English" },
  { code: "fr", displayName: "French", nativeName: "Français" },
];

function ApplicationRoute() {
  const hydrationState = Route.useLoaderData();

  return (
    <LocaleProvider
      projectId="your-project-id"
      environment="development"
      initialLocale="en"
      fallbackLocale="en"
      hydrationState={hydrationState}
      catalogEndpoint={import.meta.env.VITE_LINGUANA_API_URL}
      publishableKey={import.meta.env.VITE_LINGUANA_PUBLISHABLE_KEY}
      availableLocales={availableLocales}
    >
      <Outlet />
    </LocaleProvider>
  );
}
```

Adapt `/app` and the relative import to your existing route layout. Use the same environment in the build, embedded catalog, server loader, and provider; switch them together when targeting production.

Do not independently re-resolve the locale in the browser. `hydrationState` is authoritative for the initial render.

## 4. Test with Accept-Language

```bash title="terminal"
curl -H 'Accept-Language: fr' http://localhost:3000
```

When a French development catalog exists, both server HTML and hydration use French. When it does not, both use the embedded English state.

<ExpectedResult>
The initial locale and catalog set match on server and client. Locale switching persists a cookie and does not change the URL unless your application implements that routing policy.
</ExpectedResult>

<FailureGuide symptom="React reports a hydration mismatch" cause="The client started with a different locale or catalog set." check="Inspect the serialized LocaleHydrationState and make hydrationState authoritative over initialLocale." />

## Next

Read the [server rendering guide](/docs/build-with-code/server-rendering), then add client-side language switching with [Show translated content](/docs/build-with-code/show-translated-content).

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
Docs-scoped sitemap: [/docs/sitemap.md](/docs/sitemap.md).
Well-known sitemap: [/.well-known/sitemap.md](/.well-known/sitemap.md).
