---
title: "Next.js"
description: "Translate Next.js 16 App Router Server and Client Components with Turbopack or webpack."
canonical_url: "https://docs.linguana.dev/docs/getting-started/nextjs"
markdown_url: "https://docs.linguana.dev/docs/getting-started/nextjs.md"
x_farming_labs_generated_preamble: true
agent:
  task: "Integrate @linguanahq/next into a Next.js 16 App Router application."
  outcome: "Server and Client Components render the request language with source fallback, and next build writes a manifest."
  appliesTo:
    framework:
      - "Next.js"
    package:
      - "@linguanahq/next"
  prerequisites:
    - "Next.js 16 with the App Router, React 19, and the Node.js runtime."
    - "next.config does not use output: 'export' or cacheComponents: true."
  files:
    - "next.config.ts"
    - "app/layout.tsx"
    - ".env.local"
  commands:
    - "npm install @linguanahq/next"
    - "npm run build"
  verification:
    - "next build writes .linguana/manifest.json containing visible text."
    - "A request with Accept-Language: fr renders French when a French catalog is available."
  rollback:
    - "Remove the withLinguana wrapper and the LinguanaProvider from the root layout."
  failureModes:
    - symptom: "next build fails with Linguana requires Next.js 16."
      resolution: "Upgrade Next.js to version 16."
    - symptom: "useTranslation throws Next.js translations require LinguanaProvider in the root layout."
      resolution: "Render LinguanaProvider from @linguanahq/next/server in app/layout.tsx around children."
---

# Next.js
URL: /docs/getting-started/nextjs
LLM index: /llms.txt
Description: Translate Next.js 16 App Router Server and Client Components with Turbopack or webpack.
Related: /docs/reference/next, /docs/troubleshooting/nextjs

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

Task: Integrate @linguanahq/next into a Next.js 16 App Router application.
Outcome: Server and Client Components render the request language with source fallback, and next build writes a manifest.

### Applies To

- Framework: `Next.js`
- Package: `@linguanahq/next`

### Prerequisites

- Next.js 16 with the App Router, React 19, and the Node.js runtime.
- next.config does not use output: 'export' or cacheComponents: true.

### Files

- `next.config.ts`
- `app/layout.tsx`
- `.env.local`

### Commands

- `npm install @linguanahq/next`
- `npm run build`

### Verification

- next build writes .linguana/manifest.json containing visible text.
- A request with Accept-Language: fr renders French when a French catalog is available.

### Rollback

- Remove the withLinguana wrapper and the LinguanaProvider from the root layout.

### Failure Modes

- next build fails with Linguana requires Next.js 16. — Recovery: Upgrade Next.js to version 16.
- useTranslation throws Next.js translations require LinguanaProvider in the root layout. — Recovery: Render LinguanaProvider from @linguanahq/next/server in app/layout.tsx around children.
<!-- farming-labs:agent-contract:end -->

# Next.js

Linguana translates Next.js App Router apps without translation keys. Write ordinary JSX in Server Components and Client Components. A source loader extracts static copy before Next.js compiles it, and the runtime renders the language selected for each request, then hydrates Client Components with the same data.

The Next.js integration is a separate package, `@linguanahq/next`. It does not use `@linguanahq/vite`.

## Requirements

| Requirement | Supported |
| --- | --- |
| Next.js | 16 (`>=16 <17`) |
| React and React DOM | 19 |
| Router | App Router |
| Bundler | Turbopack (default) or webpack (`--webpack`) |
| Runtime | Node.js |
| Rendering | Request-time rendering. `output: "export"` and `cacheComponents: true` are rejected |

Pages Router, localized URL routing, Edge runtime certification, MDX extraction, and a native SWC plugin are not included. The CLI templates and `linguana link` do not support Next.js yet; set up the integration with this guide.

## 1. Install the package

<Tabs items={["npm", "pnpm", "Bun"]}>
<Tab value="npm">

```bash title="terminal"
npm install @linguanahq/next
```

</Tab>
<Tab value="pnpm">

```bash title="terminal"
pnpm add @linguanahq/next
```

</Tab>
<Tab value="Bun">

```bash title="terminal"
bun add @linguanahq/next
```

</Tab>
</Tabs>

`@linguanahq/next` ships compiled JavaScript, so Next.js needs no extra loader settings, Babel configuration, or SWC plugin.

## 2. Wrap your Next.js configuration

Start with local extraction. No account or token is required:

```ts title="next.config.ts"
import type { NextConfig } from "next";
import { withLinguana } from "@linguanahq/next/plugin";

const nextConfig: NextConfig = {
  // Your existing configuration.
};

export default withLinguana({
  projectId: "my-app",
  sourceLocale: "en",
  environment: "development",
  upload: false,
  include: ["app/**/*.{js,jsx,ts,tsx}", "components/**/*.{js,jsx,ts,tsx}"],
})(nextConfig);
```

`my-app` is a local identifier. Replace it with your dashboard project ID before connecting hosted translations.

`withLinguana(options)` returns a function that accepts your configuration object, or a configuration function, including an async one. It works in `next.config.ts`, `next.config.mjs`, and `next.config.js`:

```js title="next.config.js"
const { withLinguana } = require("@linguanahq/next/plugin");

module.exports = withLinguana({ projectId: "my-app", upload: false })({
  // Your existing configuration.
});
```

The wrapper adds the Linguana loader to both Turbopack and webpack, ahead of your existing rules, and writes the public runtime configuration to `.linguana/next/runtime.json`. Set `root` when the application root differs from the directory where you run Next.js. See the [`withLinguana` options](/docs/reference/next#withlinguanaoptions).

## 3. Add the root provider

```tsx title="app/layout.tsx"
import type { ReactNode } from "react";
import { LinguanaProvider, getLocale } from "@linguanahq/next/server";
import "@linguanahq/next/styles.css";

export default async function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang={await getLocale()}>
      <body>
        <LinguanaProvider>{children}</LinguanaProvider>
      </body>
    </html>
  );
}
```

`LinguanaProvider` resolves the language and catalogs once per request, renders Server Components in that language, and hydrates Client Components with the same state. `getLocale()` returns the same language for the `lang` attribute. The stylesheet is optional and styles the language selector.

## 4. Write your components normally

```tsx title="app/page.tsx"
export default function Page() {
  return (
    <main>
      <h1>Welcome to your store</h1>
      <input aria-label="Search" placeholder="Search products" />
      <button title="Save your changes">Save</button>
    </main>
  );
}
```

Static text in Server and Client Components is extracted automatically, along with supported attributes such as `title`, `placeholder`, `alt`, and `aria-label`. Shared components imported by both kinds of component work in both. Extraction uses the same exclusions and message IDs as the Vite integration: add `data-no-translate` to keep content as written, and use explicit messages for dynamic or rich text. See [Keep product text and code separate](/docs/build-with-code/text-and-code).

## 5. Build and check the strings

```bash title="terminal"
npm run build
```

`next build` writes `.linguana/manifest.json`. Find a visible string from your app in it, then run `next dev` and confirm the page still renders its source language. Nothing is uploaded with `upload: false`, and development servers never upload.

Add `.linguana/` to `.gitignore`. Do not commit it or `.next/`.

<ExpectedResult>
The manifest lists your app's visible text, and the app renders in its source language. Once a target catalog is available, the same pages render in that language for requests that select it.
</ExpectedResult>

## Explicit messages

Use `T` for rich text and `getTranslations()` for strings you build in server code, such as metadata:

```tsx title="app/guide/page.tsx"
import { T } from "@linguanahq/next";
import { getTranslations } from "@linguanahq/next/server";

export async function generateMetadata() {
  const { t } = await getTranslations();
  return { title: String(t("Our products")) };
}

export default function Page() {
  return (
    <T
      id="guide-link"
      defaultMessage="Read <link>the guide</link>"
      components={{ link: <a href="/guide">the guide</a> }}
    />
  );
}
```

In Client Components, use `useTranslation()` from `@linguanahq/next/client`:

```tsx title="app/greeting.tsx"
"use client";
import { T } from "@linguanahq/next";
import { useTranslation } from "@linguanahq/next/client";

export function Greeting({ name }: { name: string }) {
  const { t } = useTranslation();
  return (
    <>
      <p>{t("Welcome, {name}", { name })}</p>
      <T id="client-help" defaultMessage="Need help? Contact support." />
    </>
  );
}
```

Import `T` from `@linguanahq/next` in both environments; it selects the server or client implementation automatically. Never import `@linguanahq/next/server` into a Client Component. Message syntax, values, plurals, and rich slots match [Dynamic text and links](/docs/build-with-code/dynamic-text-and-links).

## Let people choose a language

```tsx title="app/language.tsx"
"use client";
import { LanguageSelector } from "@linguanahq/next/client";

export function Language() {
  return <LanguageSelector />;
}
```

Render the selector from any Client Component under the provider. It validates the target catalog first, stores the `linguana_locale` cookie, and refreshes server content with `router.refresh()`. Unrelated client state is preserved, and the URL does not change. If the catalog cannot be loaded, the current language stays and a diagnostic is recorded.

For a custom control, call `setLocale(code)` from `useTranslation()` and disable the control while `ready` is `false`. `availableLocales` lists the choices.

## How the language is chosen

For each request, the provider picks the first supported match from:

1. The `linguana_locale` cookie, or your `runtime.cookieName`.
2. The browser's `Accept-Language` header.
3. `runtime.defaultLocale`, or the project default from hosted configuration.
4. The source language.

Supported languages come from `runtime.availableLocales` when set. Otherwise, with a publishable key and endpoint, they come from your project's hosted runtime configuration; without those, from the embedded catalogs. The source language is always available.

## Load translations

**Embedded catalogs.** Pass verified catalogs in `runtime.catalogs`. Matching embedded catalogs take priority over hosted ones and need no network request.

**Hosted catalogs.** Set `runtime.publishableKey` and `runtime.catalogEndpoint` (your API origin) to load published catalogs. Server requests time out after five seconds, are deduplicated within one render, and are not cached by Next.js between requests. Every catalog is checked for project, environment, source language, and integrity before use.

Unavailable, invalid, or tampered catalogs render the source text. Runtime keys are [publishable keys](/docs/use-linguana/tokens-and-environments), never build tokens.

## Connect hosted translations

Upload is off by default. To upload your text, give the build your project ID, API origin, and a build-only project token. Keep these values in `.env.local` locally and in your host's build environment:

```dotenv title=".env.local — replace the placeholders"
LINGUANA_PROJECT_ID=your-project-id
LINGUANA_ENVIRONMENT=development
LINGUANA_API_URL=https://your-linguana-api.example.com
LINGUANA_PROJECT_TOKEN=your-project-token
LINGUANA_UPLOAD=true
LINGUANA_PUBLISHABLE_KEY=your-publishable-key
```

Next.js loads `.env.local` before it evaluates `next.config.ts`, so you can read the values directly:

```ts title="next.config.ts"
import type { NextConfig } from "next";
import { withLinguana } from "@linguanahq/next/plugin";

const nextConfig: NextConfig = {};

export default withLinguana({
  projectId: process.env.LINGUANA_PROJECT_ID ?? "my-app",
  environment: process.env.LINGUANA_ENVIRONMENT ?? "development",
  sourceLocale: "en",
  include: ["app/**/*.{js,jsx,ts,tsx}", "components/**/*.{js,jsx,ts,tsx}"],
  apiUrl: process.env.LINGUANA_API_URL,
  token: process.env.LINGUANA_PROJECT_TOKEN,
  upload: process.env.LINGUANA_UPLOAD === "true",
  runtime: {
    catalogEndpoint: process.env.LINGUANA_API_URL,
    publishableKey: process.env.LINGUANA_PUBLISHABLE_KEY,
  },
})(nextConfig);
```

`apiUrl` and `token` are used only by the build and never reach browser bundles. Only the `runtime` values become public. No `NEXT_PUBLIC_` prefix is needed, because the wrapper reads these values when Next.js loads its configuration. Restart `next dev` after changing them, and rebuild for production.

With `upload: true`, `next build` uploads the manifest after compilation succeeds. Then enable a language, translate, review, and publish in the dashboard as described in [Connect hosted translations](/docs/getting-started/connect-your-app). Your build and runtime must use the same project and environment as the published catalog.

### Use credentials from the CLI

`linguana link` can create a project and write a development build token for a Next.js app, although it prints Vite instructions. See [Next.js apps](/docs/cli/link#nextjs-apps). It stores the publishable key as `VITE_LINGUANA_PUBLISHABLE_KEY`, which you can read in `next.config.ts`:

```ts
runtime: {
  catalogEndpoint: process.env.LINGUANA_API_URL,
  publishableKey: process.env.VITE_LINGUANA_PUBLISHABLE_KEY,
},
```

The other values use the names in the configuration above. `env pull --environment production` writes `.env.production.local`, which Next.js loads for `next build` and `next start`. Next.js does not load `.env.staging.local`.

### Translate during development builds

Translation is a separate opt-in for development builds:

```ts
translation: {
  enabled: true,
  maxChargeMicros: 100_000,
  waitTimeoutMs: 300_000,
  publish: "development",
},
```

It requires `upload: true`, a build token, and `environment: "development"`. Production publication remains an explicit release action. Upload and translation run after successful compilation, before Next.js type checking and static generation, so they can finish even if a later build stage fails. Remote failures fail builds that opted in. Cached builds still rescan current source and remove deleted messages.

Local translation preview is not available for Next.js. Development servers never upload or request translations.

## Rendering and caching

Because the root layout reads the request's cookies and headers, routes under `LinguanaProvider` render at request time. This is what lets each visitor see their own language.

- `output: "export"` is rejected: cookie-based translation requires a Next.js server.
- `cacheComponents: true` is rejected for now.
- Do not place automatically translated, request-dependent content inside a `"use cache"` scope.

Concurrent requests in different languages are isolated from each other.

## Deploy

Deploy to any host that runs `next build` and serves the app with the Node.js runtime, such as `next start`.

- Set `LINGUANA_*` values in the host's **build** environment. Runtime options are captured when Next.js builds the app.
- Keep `LINGUANA_PROJECT_TOKEN` a build secret. Set `LINGUANA_UPLOAD=false` for builds that should not upload, such as previews.
- Use a production token, `environment: "production"`, and a production publishable key together, and publish the production catalog in the dashboard.
- Routes under the provider run on the Node.js runtime; the Edge runtime is not certified.

## Verify your integration

1. Run `npm run build` and find your visible text in `.linguana/manifest.json`.
2. Start the app and request a page with a target language:

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

   When French is a supported language, the response has `<html lang="fr">`. With an available French catalog, the text is French. If the catalog cannot be loaded, the language stays selected, the text falls back to the source language, and the server logs a `[linguana]` warning.
3. Select a language with `LanguageSelector` and confirm Server and Client Components change together, without a hydration warning.
4. Check the server log for `[linguana]` warnings, which report catalog and translation fallbacks.

<FailureGuide symptom="The page stays in the source language" cause="No catalog for the chosen language was available, or the project, environment, or publishable key does not match the published catalog." check="Check the [linguana] server warnings, confirm the language is published to the environment in next.config.ts, and restart next dev after changing configuration." />

<FailureGuide symptom="next build fails with a Linguana configuration error" cause="An option combination is invalid, such as upload without a token, or the app uses output: 'export' or cacheComponents." check="See Next.js troubleshooting for each message and its fix." />

## Example app

The Linguana repository's `examples/next-app` runs without credentials, using embedded French translations. From a repository checkout, run `bun run dev:example:next` to try it, and `bun run verify:next` to check both bundlers, server rendering, concurrent language isolation, and cached-build manifests.

<NextStep>
Look up every export in the [Next.js API reference](/docs/reference/next), or fix build errors with [Next.js troubleshooting](/docs/troubleshooting/nextjs).
</NextStep>

## 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).
