Skip to content

Lesson 3: SSR with Context Mode

In this lesson, you will learn how to configure lingui-rr for a Server-Side Rendered (SSR) application where the locale is stored in state (like a cookie or session) instead of being prefixed in the URL paths. Under this configuration, /about stays /about regardless of the language.


Step 1: Configure the Router i18n Object

Set mode: 'context' in your router configuration. This tells the middleware to skip URL-rewriting and path-prefix validation. The active locale is resolved solely from your detectors.

ts
// app/lib/i18n.ts
import {
  createLinguiRouter,
  serverDetectors,
  serverPersistence,
} from 'lingui-rr'

export const locales = ['en', 'ar']
export const defaultLocale = 'en'

export const i18n = createLinguiRouter({
  server: true, // SSR Mode
  mode: 'context', // Clean URLs (no prefix)
  locales,
  defaultLocale,
  detection: [
    serverDetectors.cookie('locale'),
    serverDetectors.acceptLanguage(),
  ],
  persistence: [serverPersistence.cookie('locale')],
  ignorePaths: [
    /^\/assets\//,
    /^\/build\//,
    /^\/favicon\.ico$/,
    /^\/robots\.txt$/,
  ],
  catalogs: {
    en: () => import('../locales/en.po'),
    ar: () => import('../locales/ar.po'),
  },
})

Step 2: Wire the Root Route (root.tsx)

Wiring up root.tsx is similar to URL-Prefix mode: export the middleware and loader.

tsx
// app/root.tsx
import {
  createLinguiMiddleware, 
  createLinguiRootLoader, 
  LinguiRouterProvider, 
} from 'lingui-rr'
import {
  Links,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
  useLoaderData,
  useRouteLoaderData,
} from 'react-router'
import { i18n } from './lib/i18n'

export const middleware = [createLinguiMiddleware(i18n)] 
export const loader = createLinguiRootLoader(i18n) 

export function Layout({ children }: { readonly children: React.ReactNode }) {
  const lingui = useRouteLoaderData<typeof loader>('root') 

  return (
    <html
      lang={lingui?.locale ?? 'en'} 
      dir={lingui?.htmlAttrs.dir ?? 'ltr'} 
      suppressHydrationWarning
    >
      <head>
        <meta charSet="utf-8" />
        <meta content="width=device-width, initial-scale=1" name="viewport" />
        <Meta />
        <Links />
      </head>
      <body>
        {children}
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  )
}

export default function App() {
  const lingui = useLoaderData<typeof loader>() 

  return (
    <LinguiRouterProvider state={lingui} >
      <Outlet />
    </LinguiRouterProvider> 
  )
}

Step 3: Revalidation in Context Mode

When a user switches their language in URL-Prefix mode (e.g. from /about to /en/about), the URL path changes. React Router detects this path change and runs the active loaders, including the root loader that returns the language catalog.

In Context Mode, switching the language updates the cookie while the URL can remain exactly the same (/about). React Router v8 revalidates after action submissions by default, so a standard locale-switching <Form method="post" action="/change-locale"> works without a custom route-level shouldRevalidate.

createLinguiShouldRevalidate(i18n) is optional. Add it only if you want a route-level guardrail that always revalidates after submissions to the locale-switching action route (default /change-locale):

tsx
import { createLinguiShouldRevalidate } from 'lingui-rr'

export const shouldRevalidate = createLinguiShouldRevalidate(i18n)

For every non-locale action/navigation, the helper defers to React Router's defaultShouldRevalidate value. That means v8 call-site opt-outs still work:

tsx
<Form method="post" action="/local-mutation" defaultShouldRevalidate={false} />

Do not set defaultShouldRevalidate={false} on your locale-switching form unless you also handle refreshing the Lingui root loader yourself.

Released under the MIT License.