Next.js
Fluenti integrates with Next.js App Router via @fluenti/next, supporting both Client Components and React Server Components (RSC). For general SSR concepts, see the SSR & Hydration guide.
Recommended Setup with withFluenti
Section titled “Recommended Setup with withFluenti”The withFluenti() wrapper in next.config.ts is the recommended way to set up Fluenti in Next.js:
import { withFluenti } from '@fluenti/next'
export default withFluenti()({ reactStrictMode: true,})What it does
Section titled “What it does”withFluenti() performs three things at build time:
- Generates a server module (
node_modules/.fluenti/server.js) that bundlescreateServerI18n, message imports, and aFluentProvidercomponent — aliased as@fluenti/next/__generated - Registers a webpack loader that transforms
t`...`tagged templates andt()calls into__i18n.t()lookups, auto-detecting server vs client context - Adds a resolve alias so
@fluenti/next/__generatedpoints to the generated module
The loader detects context automatically:
- Files with
'use client'→ client mode (reads fromglobalThis.__fluenti_i18n, set byI18nProvider) - Files with
'use server'or insideapp/→ server mode (reads fromReact.cache()-scoped store)
Config options
Section titled “Config options”All options are optional — defaults are read from fluenti.config.ts:
export default withFluenti({ locales: ['en', 'ja', 'zh-CN'], defaultLocale: 'en', resolveLocale: './lib/resolve-locale',})({ reactStrictMode: true })| Option | Type | Default | Description |
|---|---|---|---|
locales | string[]? | from config file | Override locale list |
defaultLocale | string? | from config file | Override default/fallback locale |
compiledDir | string? | from config file | Path to compiled message catalogs |
resolveLocale | string? | reads locale cookie | Module path that default-exports () => string | Promise<string> |
serverModule | string? | auto-generated | Custom server module path (skips generation) |
serverModuleOutDir | string? | node_modules/.fluenti | Where to write generated files |
dateFormats | DateFormatOptions? | — | Custom date format presets |
numberFormats | NumberFormatOptions? | — | Custom number format presets |
fallbackChain | Record<string, string[]>? | — | Locale fallback chains |
FluentProvider
Section titled “FluentProvider”withFluenti() generates a FluentProvider component that wraps both the client I18nProvider and server locale setup:
import { cookies } from 'next/headers'import { getDirection } from '@fluenti/core'// @ts-expect-error — generated at build time by withFluenti()import { FluentProvider } from '@fluenti/next/__generated'
export default async function RootLayout({ children }: { children: React.ReactNode }) { const locale = (await cookies()).get('locale')?.value ?? 'en' return ( <html lang={locale} dir={getDirection(locale)}> <body> <FluentProvider locale={locale}> {children} </FluentProvider> </body> </html> )}Client Components
Section titled “Client Components”Client components use t`...` for translations. The withFluenti() plugin injects the macro automatically:
'use client'
import { useRouter } from 'next/navigation'import { useI18n } from '@fluenti/react'
export default function Home() { const { setLocale, preloadLocale } = useI18n() const router = useRouter()
const switchLocale = async (loc: string) => { document.cookie = `locale=${loc};path=/;max-age=31536000` await setLocale(loc) router.refresh() }
return ( <div> <h1>{t`Welcome to my app`}</h1> <button onMouseEnter={() => preloadLocale('zh-CN')} onClick={() => switchLocale('zh-CN')} >中文</button> </div> )}React Server Components (RSC)
Section titled “React Server Components (RSC)”Server Components use the same t`...` syntax. The withFluenti() plugin detects the component type and applies the correct server-side transform:
export default async function AboutPage() { return ( <div> <h1>{t`About us`}</h1> <p>{t`We build great software.`}</p> </div> )}For advanced RSC patterns, you can also use getI18n() and the server-side components directly:
import { getI18n, Trans, Plural } from '@/lib/i18n.server'
export default async function DashboardPage() { const { t } = await getI18n() return ( <div> <h1>{t('Dashboard')}</h1> <Trans>Read the <a href="/docs">documentation</a>.</Trans> <Plural value={5} one="# item" other="# items" /> </div> )}Custom Locale Resolution
Section titled “Custom Locale Resolution”By default, withFluenti() reads the locale from a locale cookie. To resolve the locale from a database, JWT, or external API, pass a module path:
export default withFluenti({ resolveLocale: './lib/resolve-locale',})({ reactStrictMode: true })The module must default-export an async function returning the locale string:
import { cookies } from 'next/headers'import { auth } from './auth'
export default async function resolveLocale(): Promise<string> { const session = await auth() if (session?.user?.locale) return session.user.locale const cookieStore = await cookies() return cookieStore.get('locale')?.value ?? 'en'}This is critical for Server Actions, which skip the layout and rely on resolveLocale. See Custom Locale Resolution for a full example.
Generated Server Module
Section titled “Generated Server Module”For full control, you can bypass withFluenti() generation and create your own server module with createServerI18n:
import { createServerI18n } from '@fluenti/react/server'
export const { setLocale, getI18n, Trans, Plural, DateTime, NumberFormat } = createServerI18n({ loadMessages: (locale) => import(`../locales/compiled/${locale}.js`), fallbackLocale: 'en', resolveLocale: async () => { const { cookies } = await import('next/headers') return (await cookies()).get('locale')?.value ?? 'en' },})Hybrid: Client + Server in the same app
Section titled “Hybrid: Client + Server in the same app”You can use both patterns together. The FluentProvider in your layout handles both:
import { cookies } from 'next/headers'import { getDirection } from '@fluenti/core'// @ts-expect-error — generated at build timeimport { FluentProvider } from '@fluenti/next/__generated'
export default async function RootLayout({ children }: { children: React.ReactNode }) { const locale = (await cookies()).get('locale')?.value ?? 'en' return ( <html lang={locale} dir={getDirection(locale)}> <body> <FluentProvider locale={locale}> {children} </FluentProvider> </body> </html> )}- Server Components use
t`...`(auto-transformed) or import from@/lib/i18n.server - Client Components use
t`...`(auto-transformed) or import from@fluenti/react
Server Actions
Section titled “Server Actions”Server Actions are independent requests that skip the layout’s setLocale() call. The resolveLocale function is called automatically to determine the active locale.
Using t`...` (recommended)
Section titled “Using t`...` (recommended)”The t`...` macro works in 'use server' files — the plugin detects the directive and injects the server-side i18n accessor automatically:
'use server'
export async function greetAction() { return t`Hello from server action`}
export async function searchAction(query: string) { if (!query) return { error: t`Please enter a search term` } // ...}No imports needed — withFluenti() handles everything.
Using getI18n() manually
Section titled “Using getI18n() manually”For cases where you need the full i18n instance (e.g. dynamic message keys):
'use server'import { getI18n } from '@/lib/i18n.server'
export async function greetAction() { const { t } = await getI18n() // resolveLocale called automatically return t('Hello from server action')}