HomeSandboxShowcaseAppDocBlog
    • EnglishEnglish
      EN
    • русскийRussian
      RU
    • 日本語Japanese
      JA
    • françaisFrench
      FR
    • 한국어Korean
      KO
    • 中文Chinese
      ZH
    • españolSpanish
      ES
    • DeutschGerman
      DE
    • العربيةArabic
      AR
    • italianoItalian
      IT
    • British EnglishBritish English
      EN-GB
    • portuguêsPortuguese
      PT
    • हिन्दीHindi
      HI
    • TürkçeTurkish
      TR
    • polskiPolish
      PL
    • IndonesiaIndonesian
      ID
    • Tiếng ViệtVietnamese
      VI
    • українськаUkrainian
      UK
    /
    Filter docs by framework
    Alt+←
    Why Intlayer ?
    Get Started
    Concept
    • How Intlayer Works
    • Configuration
    • TestFillBuildWatchExtractLoginPushPullConfigurationListVersionEditorLiveDebugDoc ReviewDoc TranslateSDK
    • Visual Editor
    • CMS
    • CI/CD Integration
    • TranslationPluralEnumerationConditionGenderInsertionFileNestingMarkdownHTMLFunction Fetching
    • Per Locale File
    • Compiler
    • Auto Fill
    • Testing
    • Bundle Optimization
    Environment
    • Next.js 14 and App Router
      Next.js 15
      Next.js no locale path
      Next.js and Page Router
      Compiler
    • Tanstack Start Solid
    • Astro and React
      Astro and Svelte
      Astro and Vue
      Astro and Solid
      Astro and Preact
      Astro and Lit
      Astro and Vanilla JS
    • React Router v7
      React Router v7 (fs-routes)
      Compiler
    • Nuxt and Vue
    • Vite and Solid
    • SvelteKit
    • Vite and Preact
    • Vite and Vanilla JS
    • Vite and Lit
    • Angular 19 (Webpack)
      Analog
    • React CRA
    • React Native and Expo
    • Express.js
      NestJS
      Fastify
      Hono
      Adonis
    • Lynx and React
    Plugins
    • JSON
    • gettext (.po)
    VS Code Extension
    Agent
    • MCP Server
    • Agent skills
    Releases
    • v8
    • v7
    • v6
    Benchmark
    • Next.js
    • TanStack
    • Vue
    • Solid
    • Svelte
    Blog
    Ask a question
    1. Documentation
    2. Benchmark
    3. TanStack
    Author: Aymeric PINEAU
    Creation:2026-04-20Last update:2026-05-18
    See the application template on GitHub

    This page has an application template available.

    Reference this doc to your favorite AI assistant
    ChatGPT
    Claude
    DeepSeek
    Google AI mode
    Gemini
    Perplexity
    Mistral
    Grok

    Ask your question and get a summary of the document by referencing this page and the AI provider of your choice

    Version History

    1. "Add GitHub star comparative"
      v8.9.818/05/2026
    2. "Init benchmark"
      v8.7.506/01/2026

    The content of this page was translated using an AI.

    See the last version of the original content in English
    Edit this doc

    If you have an idea for improving this documentation, please feel free to contribute by submitting a pull request on GitHub.

    GitHub link to the documentation
    Copy

    Copy doc Markdown to clipboard

    TanStack Start i18n Libraries - 2026 Benchmark Report

    This page is a benchmark report for i18n solutions on TanStack Start.

    Table of Contents

    Interactive Benchmark

    Results reference:

    intlayer.org
    See complete benchmark data

    See complete benchmark repository here.

    Introduction

    Internationalisation solutions are among the heaviest dependencies in a React app. On TanStack Start, the main risk is shipping unnecessary content: translations for other pages and other locales in a single route’s bundle.

    As your app grows, that problem can quickly blow up the JavaScript sent to the client and slow down navigation.

    In practice, for the least optimised implementations, an internationalised page can end up several times heavier than the version without i18n.

    The other impact is on developer experience: how you declare content, types, namespace organisation, dynamic loading, and reactivity when the locale changes.

    TL;DR

    • Intlayer: Provides the best performance and smallest bundle size (v8.7.12) for TanStack Start.
    • react-i18next & use-intl: Mature alternatives with large ecosystems, but significantly heavier and more complex to optimise.
    • Paraglide: Innovative tree-shaking idea that does not work in practice. Complex DX and reactivity overhead in TanStack Start.
    • Avoid: General Translation (GT) and Lingo.dev due to severe performance issues, AI quota limits, and vendor lock-in.

    Test your app

    To quickly spot i18n leakage issues, I set up a free scanner available here.

    intlayer.org

    The problem

    Two levers are essential to limit the cost of a multilingual app:

    • Split content by page / namespace so you do not load whole dictionaries when you do not need them
    • Load the right locale dynamically, only when needed

    Understanding the technical limitations of these approaches:

    Dynamic loading

    Without dynamic loading, most solutions keep messages in memory from the first render, which adds significant overhead for apps with many routes and locales.

    With dynamic loading, you accept a trade-off: less initial JS, but sometimes an extra request when switching language.

    Content splitting

    Syntaxes built around const t = useTranslation() + t('a.b.c') are very convenient but often encourage keeping large JSON objects at runtime. That model makes tree-shaking hard unless the library offers a real per-page split strategy.

    Methodology

    For this benchmark, we compared the following libraries:

    • Base App (No i18n library)
    • react-intlayer (v8.7.12)
    • react-i18next (v17.0.2)
    • use-intl (v4.9.1)
    • @lingui/core (v5.3.0)
    • @inlang/paraglide-js (v2.15.1)
    • @tolgee/react (v7.0.0)
    • react-intl (v10.1.1)
    • wuchale (v0.22.11)
    • gt-react (vlatest)
    • lingo.dev (v0.133.9)

    The framework is TanStack Start with a multilingual app of 10 pages and 10 languages.

    We compared four loading strategies:

    Show all table content

    Open the table in a modal to view all data content clearly

    Strategy No namespaces (global) With namespaces (scoped)
    Static loading Static: Everything in memory at startup. Scoped static: Split by namespace; everything loaded at startup.
    Dynamic loading Dynamic: On-demand loading per locale. Scoped dynamic: Granular loading per namespace and locale.

    Strategy summary

    • Static: Simple; no network latency after the initial load. Downside: large bundle size.
    • Dynamic: Reduces initial weight (lazy-loading). Ideal when you have many locales.
    • Scoped static: Keeps code organised (logical separation) without complex extra network requests.
    • Scoped dynamic: Best approach for code splitting and performance. Minimises memory by loading only what the current view and active locale need.

    GitHub STARs

    GitHub stars are a strong indicator of a project's popularity, community trust, and long-term relevance. While not a direct measure of technical quality, they reflect how many developers find the project useful, follow its progress, and are likely to adopt it. For estimating the value of a project, stars help compare traction across alternatives and provide insights into ecosystem growth.

    Star History Chart

    Results in detail

    1 - Solutions to avoid

    Some solutions, such as gt-react or lingo.dev, are clearly ones to steer clear of. They combine vendor lock-in with polluting your codebase. Worse: despite many hours trying to implement them, I never got them working properly on TanStack Start (similar to Next.js with gt-next).

    Issues encountered:

    (General Translation) (gt-react@latest):

    • For an app around 110kb, gt-react can add more than 440kb extra (order of magnitude seen on the Next.js implementation in the same benchmark).
    • Quota Exceeded, please upgrade your plan on the very first build with General Translation.
    • Translations are not rendered; I get the error Error: <T> used on the client-side outside of <GTProvider>, which seems to be a bug in the library.
    • While implementing gt-tanstack-start-react, I also came across an issue with the library: does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser, which was making the application break. After reporting this issue, the maintainer fixed it within 24 hours.
    • These libraries use an anti-pattern through the initializeGT() function, blocking the bundle from tree-shaking cleanly.

    (Lingo.dev) ([email protected]):

    • AI quota exceeded (or blocking server dependency), making build / production risky without paying.
    • The compiler was missing almost 40% of the translated content. I had to rewrite all .map into flat component blocks to make it work.
    • Their CLI is buggy and used to reset the config file for no reason.
    • At build, it totally erased the generated JSONs when there was new content added. As a result, you could end up with only a few keys erasing hundreds of existing keys.
    • I met reactivity issues with the library on TanStack Start: on locale change I had to force rerendering of the provider to make it work.

    2 - Experimental solutions

    (Wuchale) ([email protected]):

    The idea behind Wuchale is interesting but not yet a viable solution. I hit reactivity issues with the library and had to force rerendering of the provider to get the app working on TanStack Start. The documentation is also fairly unclear, which makes onboarding harder.

    3 - Acceptable solutions

    (Paraglide) (@inlang/[email protected]):

    Paraglide offers an innovative, well-thought-out approach. Even so, in this benchmark the tree-shaking their company advertises did not work for my Next.js implementation or for TanStack Start. The workflow and DX are also more complex than other options. Personally I am not a fan of having to regenerate JS files before every push, which creates constant merge conflict risk for developers via PRs.

    Note on paraglide: the solution injects code into your codebase for imports; as a result, the 'lib size' metric in the benchmark report is almost 0. Code generation is a good thing, because the function used will include only the necessary logic (prefix everywhere vs no prefix, cookie vs storage, etc.). In comparison, Intlayer performs this filtering via environment variable injections in the build to force the bundler to tree-shake the content depending on the logic. Thanks to this, paraglide and intlayer end up being 6 to 10 times lighter solutions than i18next or next-intl.

    (Tolgee) (@tolgee/[email protected]):

    Tolgee addresses many of the issues mentioned earlier. I found it harder to get started with than other tools with similar approaches. It does not provide type safety, which also makes catching missing keys at compile time much harder. I had to wrap Tolgee’s APIs with my own to add missing-key detection.

    On TanStack Start I also had reactivity problems: on locale change I had to force the provider to rerender and subscribe to locale-change events so loading in another language behaved correctly.

    (use-intl) ([email protected]):

    use-intl is the most fashionable “intl” piece in the React ecosystem (same family as next-intl) and is often pushed by AI agents, but in my view wrongly so in a performance-first setting. Getting started is fairly simple. In practice, the process to optimise and limit leakage is quite complex. Likewise, combining dynamic loading + namespacing + TypeScript types slows development a lot.

    On TanStack Start you avoid Next.js-specific traps (setRequestLocale, static rendering), but the core issue is the same: without strict discipline, the bundle quickly carries too many messages and per-route namespace maintenance becomes painful.

    (react-i18next) ([email protected]):

    react-i18next is probably the most popular option because it was among the first to serve JavaScript app i18n needs. It also has a wide set of community plugins for specific problems.

    Still, it shares the same major downsides as stacks built on t('a.b.c'): optimisations are possible but very time-consuming, and large projects risk bad practices (namespaces + dynamic loading + types).

    Message formats also diverge: use-intl uses ICU MessageFormat, while i18next uses its own format-which complicates tooling or migrations if you mix them.

    (Lingui) (@lingui/[email protected]):

    Lingui is often praised. Personally I found the workflow around lingui extract / lingui compile more complex than other approaches, without a clear upside in this TanStack Start benchmark. I also noticed inconsistent syntaxes that confuse AIs (e.g. t(), t'', i18n.t(), <Trans>).

    (react-intl) ([email protected]):

    react-intl is a performant implementation from the Format.js team. The DX stays verbose: const intl = useIntl() + intl.formatMessage({ id: "xx.xx" }) adds complexity, extra JavaScript work, and ties the global i18n instance to many nodes in the React tree.

    4 - Recommendations

    This TanStack Start benchmark has no direct equivalent to next-translate (Next.js plugin + getStaticProps). For teams that really want a t() API with a mature ecosystem, react-i18next and use-intl remain “reasonable” choices, but expect to invest a lot of time optimising to avoid leakage.

    (Intlayer) ([email protected]):

    I will not personally judge react-intlayer for objectivity’s sake, since it is my own solution.

    Personal note

    This note is personal and does not affect the benchmark results. Still, in the i18n world you often see consensus around a pattern like const t = useTranslation('xx') + <>{t('xx.xx')}</> for translated content.

    In React apps, injecting a function as a ReactNode is, in my view, an anti-pattern. It also adds avoidable complexity and JavaScript execution overhead (even if it is barely noticeable).

    Next.js
    Vue
    Alt+→

    On this page

      Discussions are anonymous and regularly reviewed to address common issues. Feel free to share feature ideas, feedback on the documentation, or anything related to Intlayer, we use this input to shape our roadmap and improve the product.

      Dynamic JSON loading

      Lazy-loads translations at runtime

      Scoped JSON (namespacing)

      Per-page translation namespaces

      I18n Performance Benchmark

      No data available

      What is this metric?

      The total gzip-compressed size of the internationalisation library bundle. It only includes the provider and content retrieval logic after tree-shaking and minification.

      Why is it important?

      A smaller library size reduces the initial JavaScript payload, leading to faster download and execution times on the client.

      View as