Locale switching updates content but not UI translations (SSR)

4 min de lecturaIntermedio

You switch languages and your CMS content (collection cards, articles) updates correctly — but UI strings from useTranslations() (headings like "How can we help?", search placeholders, CTA buttons) stay in the old language.

Why this happens

In SSR frameworks like TanStack Start, the root route loader fetches translations during server-side rendering. After hydration, the app switches to SPA mode — subsequent navigations don't hit the server, they use the client-side router.

Most routers have a cache/stale time setting that controls whether the loader re-runs on navigation. If this is set too high (or to Infinity), the router considers the initial SSR result "forever fresh" and never re-runs the loader on the client — even when the locale changes.

The result: loaderData.messages stays frozen at the initial SSR locale's value, and BetterI18nProvider keeps receiving stale translations.

The fix

TanStack Router / TanStack Start

In your root route configuration, set staleTime: 0:

// src/routes/__root.tsx
export const Route = createRootRouteWithContext<RouterContext>()({
  staleTime: 0, // re-run loader on every navigation (locale switch needs this)
  loader: async ({ context }) => {
    const messages = await getMessages({
      project: 'your-org/your-project',
      locale: context.locale,
    });
    return { messages, locale: context.locale };
  },
});

Won't this hurt performance? No. getMessages() uses an in-memory TtlCache (60 seconds). Same-locale navigations hit the cache (~0ms). Only an actual locale change triggers a CDN fetch.

Remix / React Router

In Remix, loaders re-run by default on navigation — this issue is less common. But if you've added custom caching with shouldRevalidate, make sure locale changes trigger revalidation:

export function shouldRevalidate({ currentParams, nextParams }) {
  // Always revalidate when locale changes
  if (currentParams.locale !== nextParams.locale) return true;
  return false;
}

Next.js App Router

Next.js App Router re-runs server components on navigation, so this issue typically doesn't occur. If you're using the Pages Router with getServerSideProps, it also re-runs on every navigation.

If you're using getStaticProps with ISR, translations are baked at build time per locale — this is expected behavior, not a bug.

How to verify the fix

  1. Open your app and navigate to any page
  2. Switch to a different locale (e.g., English → Spanish)
  3. Check that both UI strings ("How can we help?" → "¿Cómo podemos ayudarte?") and CMS content update
  4. Open the Network tab — you should see a CDN fetch for the new locale (e.g., es/translations.json)
  5. Switch back to the original locale — it should load instantly from cache

Defense in depth

The @better-i18n/use-intl provider (v0.5+) includes automatic stale-detection: if the locale prop changes but messages doesn't, the provider fetches fresh translations from the CDN. This is a safety net — the primary fix should always be ensuring your router re-runs the loader on locale changes.