Skip to content

SSR

SSR apps should load translations through @nano_kit/query so locale data can be preloaded before HTML is rendered and dehydrated into the SSR payload. Without that preload, the server can render fallback or wrong-locale messages and the client has to fix them after hydration.

This page only covers the @nano_kit/intl side. Query client setup, SSR, and dehydration are covered in the @nano_kit/query documentation.

Here is an example of how to set up an Intl$ DI store that loads translations with @nano_kit/query and combines them with SSR-aware locale detection and cookie storage. It usually combines:

  • Locales$ from SSR Locale for request-bound language detection.
  • CookieStore$ from SSR Cookies for persisted locale selection.
  • query(...) for cached translation loading.
  • intl($locale, loader) for message access.
import { inject } from '@nano_kit/store'
import { queryKey } from '@nano_kit/query'
import { intl } from '@nano_kit/intl'
import {
Locales$,
CookieStore$,
browserLocale,
cookieStored
} from '@nano_kit/platform-web'
import { Client$ } from './query'
import {
type SupportedLocale,
type Translations,
load,
supportedLocales
} from '../translations'
const TranslationsKey = queryKey<[locale: SupportedLocale], Translations>('translations')
export function Intl$() {
const locales = inject(Locales$)
const cookieStore = inject(CookieStore$)
const { query } = inject(Client$)
const $locale = cookieStored(cookieStore, {
name: 'locale',
path: '/',
sameSite: 'lax'
}, browserLocale(locales, supportedLocales, 'en'))
const {
messages,
$loading
} = intl(
$locale,
query(TranslationsKey, [$locale], load)
)
return {
supportedLocales,
messages,
$locale,
$loading
}
}

…and then use Intl$ on some app page:

/* ... */
function Messages$() {
const { messages } = inject(Intl$)
return messages('event', {
/* ... */
})
}
export function Stores$() {
const [$t] = inject(Messages$)
const { $event } = inject(EventDetails$)
return [$t, $event]
}
/* ... */

In this setup, translations are loaded through query(...). The active locale is read from the locale cookie; when the cookie is missing, browserLocale(...) resolves it from the request Accept-Language header. intl(...) then exposes messages, and each page or layout declares the messages it needs in a local Messages$ store.

For SSR preload, return $t from Stores$ the same way you return regular data signals that should be awaited and dehydrated. This lets the renderer load translations before producing HTML, so the response already contains messages for the resolved locale.