React Router v7 framework-mode + Locize example
A minimal React Router v7 (framework mode) sample showing how to wire remix-i18next (v7.x, the RR-v7-targeting major) with Locize for server-side language detection plus client-side translation loading.
Stack: React Router 7.15 (framework mode, middleware enabled) ·
remix-i18next 7.5 · i18next 26 · react-i18next 17 ·
i18next-locize-backend 10 · locize 4.0.23 (with the SSR-safe
isInIframe fix shipped today) · React 19 · Vite 7.
Migrating from Remix v2? See the companion
locize-remix-i18next-examplewhich stays on Remix v2 +remix-i18next6.x for projects that haven't moved to React Router v7 framework mode yet.
- Create a free account and a project at https://www.locize.com/?from=locize-react-router-example,
then grab your project id (and an API key if you want
saveMissingwrites during local dev). - Either edit
app/locize.config.tsandpackage.json'sdownloadLocales/syncLocalesscripts, or keep the shipped demo-project credentials to try it out first. - Pre-bundle the translations:
npm run downloadLocales— pulls the latest published JSON from Locize intoapp/locales/(the JSON files there are committed as sane defaults so the example builds without a Locize account). npm install && npm run dev, then open http://localhost:3000.
React Router v7's framework mode runs the same React tree on the server and on the client. We use different translation sources on each side to get the best of both:
createI18nextMiddleware from remix-i18next/middleware (the new
RR-v7 middleware pattern, opt-in via future.v8_middleware: true in
react-router.config.ts) detects the locale from the request (cookie
→ URL param → Accept-Language) and seeds a per-request i18next
instance.
Resources come from the statically-imported JSON in
app/locales/index.ts — i.e. the JSON files
in app/locales/ that locize download placed there at build
time. No runtime calls to the Locize CDN from the server. This is
important on serverless platforms (Vercel/Netlify/Cloudflare Workers)
where each request would otherwise be a fresh cold-start re-download.
getLocale(context) and getInstance(context) (returned by
createI18nextMiddleware) are how loaders and entry.server.tsx
access what the middleware decided.
After the SSR-rendered HTML reaches the browser, i18next initialises
with i18next-locize-backend directly against the Locize CDN. This
means content updates published in Locize show up on the next page
view without a redeploy. The
locize in-context editor plugin
(append ?locize=true to the URL) and dev-only
locize-lastused for
unused-key tracking round out the integration.
hydrateRoot is wrapped in startTransition per React 19 + RR v7
guidance.
Set in app/entry.client.tsx via saveMissing: !isProduction. When
i18next requests a key that the loaded JSON doesn't yet have, it
pushes the key to Locize so translators can fill it in — analogous
to a build-time extraction step, but live during dev.
The locize package's isInIframe detection was fixed in 4.0.23
(changelog)
to correctly return false in Node 20+ SSR builds. Prior versions
incorrectly evaluated self !== top (since self is now globalThis
in modern Node) and made locize believe it was inside the Locize
editor iframe during SSR rendering. The fix matters anywhere React
Router v7 renders on the server — i.e. always in framework mode.
The write-enabled apiKey is hardcoded in
app/locize.config.ts only because the demo
project is intentionally public. For your own project, source the
dev apiKey from a git-ignored env file (e.g. read from
import.meta.env.VITE_LOCIZE_APIKEY in dev, omit in prod) and never
ship a write-enabled key in production builds.
| Command | What it does |
|---|---|
npm run dev |
React Router dev server on http://localhost:3000 |
npm run build |
Production build into build/ |
npm start |
Run the production build via @react-router/serve |
npm run typecheck |
react-router typegen && tsc |
npm run downloadLocales |
locize download — pull translations into app/locales/ |
npm run syncLocales |
locize sync — two-way sync (dry-run by default) |
The example talks to the Pro CDN (api.locize.app) by default
because the shipped demo project lives there. If your project is on
the Standard CDN (api.lite.locize.app, the default for new Locize
projects), set cdnType: 'standard' in
app/locize.config.ts. See
CDN types: Standard vs. Pro
for the full comparison.
- Locize platform docs
- remix-i18next documentation
- Sergio Xalambri's reference RR v7 example
- Remix v2 alternative (for projects not yet on RR v7): locize-remix-i18next-example
- Next.js + Locize alternative: next-i18next-locize