---
createdAt: 2026-04-20
updatedAt: 2026-05-18
title: Giải pháp i18n tốt nhất cho TanStack Start năm 2026 - Báo cáo Benchmark
description: So sánh các thư viện quốc tế hóa cho TanStack Start như react-i18next, use-intl và Intlayer. Báo cáo hiệu năng chi tiết về kích thước gói bundle, rò rỉ dữ liệu và tính phản ứng.
keywords:
  - benchmark
  - i18n
  - intl
  - tanstack
  - hiệu năng
  - intlayer
slugs:
  - doc
  - benchmark
  - tanstack
author: Aymeric PINEAU
applicationTemplate: https://github.com/intlayer-org/benchmark-i18n-tanstack-start-template
history:
  - version: 8.9.8
    date: 2026-05-18
    changes: "Thêm so sánh sao GitHub"
  - version: 8.7.5
    date: 2026-01-06
    changes: "Khởi tạo benchmark"
---

# Thư viện i18n cho TanStack Start - Báo cáo Benchmark 2026

Trang này là báo cáo benchmark cho các giải pháp i18n trên TanStack Start.

## Mục lục

<Toc/>

## Benchmark tương tác

<I18nBenchmark framework="tanstack" vertical/>

## Tham chiếu kết quả:

<ClickToOpenIframe
  src="https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-tanstack.md"
  width="100%"
  height="600px"
  style="border:none;"
/>

> [Xem dữ liệu benchmark đầy đủ](https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-tanstack.md)

Xem kho lưu trữ benchmark đầy đủ [tại đây](https://github.com/intlayer-org/benchmark-i18n/tree/main).

## Giới thiệu

Các giải pháp quốc tế hóa là một trong những phụ thuộc (dependency) nặng nề nhất trong một ứng dụng React. Trên TanStack Start, rủi ro chính là gửi đi nội dung không cần thiết: các bản dịch cho các trang khác và các ngôn ngữ khác trong gói bundle của một route duy nhất.

Khi ứng dụng của bạn phát triển, vấn đề đó có thể nhanh chóng làm tăng vọt lượng JavaScript được gửi đến máy khách và làm chậm quá trình điều hướng.

Trong thực tế, đối với các triển khai ít được tối ưu hóa nhất, một trang đa ngôn ngữ có thể nặng hơn nhiều lần so với phiên bản không có i18n.

Tác động khác là đối với trải nghiệm phát triển (DX): cách bạn khai báo nội dung, các kiểu dữ liệu, tổ chức namespace, tải động và tính phản ứng khi ngôn ngữ thay đổi.

## TL;DR

- **Intlayer**: Cung cấp hiệu năng tốt nhất và kích thước bundle nhỏ nhất (v8.7.12) cho TanStack Start.
- **react-i18next** & **use-intl**: Các lựa chọn thay thế thuần thục với hệ sinh thái lớn, nhưng nặng hơn đáng kể và phức tạp hơn để tối ưu hóa.
- **Paraglide**: Ý tưởng tree-shaking sáng tạo nhưng không hoạt động trong thực tế. DX phức tạp và chi phí phản ứng trên TanStack Start.
- **Cần tránh**: **General Translation (GT)** và **Lingo.dev** do các vấn đề hiệu năng nghiêm trọng, giới hạn hạn mức AI và bị ràng buộc vào nhà cung cấp (vendor lock-in).

## Kiểm tra ứng dụng của bạn

Để nhanh chóng phát hiện các vấn đề rò rỉ i18n, tôi đã thiết lập một trình quét miễn phí có sẵn [tại đây](https://intlayer.org/i18n-seo-scanner).

<ClickToOpenIframe src="https://intlayer.org/i18n-seo-scanner" width="100%" height="600px" style="border:none;"/>

## Vấn đề

Hai đòn bẩy là cần thiết để hạn chế chi phí của một ứng dụng đa ngôn ngữ:

- Chia tách nội dung theo trang / namespace để bạn không tải toàn bộ từ điển khi không cần dùng đến.
- Tải đúng ngôn ngữ một cách linh hoạt, chỉ khi cần thiết.

Hiểu các hạn chế kỹ thuật của các phương tiếp cận này:

**Tải động (Dynamic loading)**

Nếu không có tải động, hầu hết các giải pháp giữ các thông điệp trong bộ nhớ ngay từ lần render đầu tiên, điều này thêm vào chi phí đáng kể cho các ứng dụng có nhiều route và ngôn ngữ.

Với tải động, bạn chấp nhận một sự đánh đổi: ít mã JS ban đầu hơn, nhưng đôi khi có thêm một yêu cầu khi chuyển đổi ngôn ngữ.

**Chia tách nội dung (Content splitting)**

Các cú pháp được xây dựng xung quanh `const t = useTranslation()` + `t('a.b.c')` rất thuận tiện nhưng thường khuyến khích việc giữ các đối tượng JSON lớn tại thời điểm chạy (runtime). Mô hình đó khiến việc loại bỏ mã thừa (tree-shaking) trở nên khó khăn trừ khi thư viện cung cấp một chiến lược chia tách thực sự theo từng trang.

## Phương pháp đo lường

Đối với benchmark này, chúng tôi đã so sánh các thư viện sau:

- `Base App` (Không sử dụng thư viện i18n)
- `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)

Framework được sử dụng là `TanStack Start` với một ứng dụng đa ngôn ngữ gồm **10 trang** và **10 ngôn ngữ**.

Chúng tôi đã so sánh **bốn chiến lược tải**:

| Chiến lược   | Không có namespace (global)                         | Có namespace (scoped)                                                        |
| :----------- | :-------------------------------------------------- | :--------------------------------------------------------------------------- |
| **Tải tĩnh** | **Static**: Mọi thứ nằm trong bộ nhớ khi khởi động. | **Scoped static**: Chia tách theo namespace; mọi thứ được tải khi khởi động. |
| **Tải động** | **Dynamic**: Tải theo yêu cầu cho mỗi ngôn ngữ.     | **Scoped dynamic**: Tải chi tiết theo từng namespace và ngôn ngữ.            |

## Tóm tắt chiến lược

- **Static**: Đơn giản; không có độ trễ mạng sau lần tải đầu tiên. Nhược điểm: kích thước gói bundle lớn.
- **Dynamic**: Giảm bớt gánh nặng ban đầu (lazy-loading). Lý tưởng khi bạn có nhiều ngôn ngữ.
- **Scoped static**: Giữ cho mã được tổ chức (tách biệt logic) mà không cần các yêu cầu mạng bổ sung phức tạp.
- **Scoped dynamic**: Phương pháp tốt nhất cho việc _chia tách mã_ (code splitting) và hiệu năng. Giảm thiểu bộ nhớ bằng cách chỉ tải những gì cần thiết cho chế độ xem hiện tại và ngôn ngữ đang hoạt động.

## Sao GitHub

Sao GitHub là một chỉ số mạnh mẽ về mức độ phổ biến của dự án, sự tin tưởng của cộng đồng và mức độ phù hợp lâu dài. Mặc dù không phải là thước đo trực tiếp về chất lượng kỹ thuật, chúng phản ánh số lượng nhà phát triển thấy dự án hữu ích, theo dõi tiến trình của nó và có khả năng áp dụng nó. Để ước tính giá trị của một dự án, các ngôi sao giúp so sánh sức hút giữa các lựa chọn thay thế và cung cấp thông tin chi tiết về sự phát triển của hệ sinh thái.

[![Star History Chart](https://api.star-history.com/chart?repos=formatjs%2Fformatjs%2Ci18next%2Freact-i18next%2Clingui%2Fjs-lingui%2Camannn%2Fnext-intl%2Copral%2paraglide-js%2Ctolgee%2Ftolgee-js%2Caymericzip%2Fintlayer&type=date&legend=top-left)](https://www.star-history.com/#formatjs/formatjs&i18next/react-i18next&lingui/js-lingui&amannn/next-intl&opral/paraglide-js&tolgee/tolgee-js&aymericzip/intlayer)

## Kết quả chi tiết

### 1 - Các giải pháp cần tránh

Một số giải pháp, chẳng hạn như `gt-react` hoặc `lingo.dev`, rõ ràng là những lựa chọn nên tránh xa. Chúng kết hợp việc phụ thuộc vào nhà cung cấp với việc làm ô nhiễm mã nguồn của bạn. Tệ hơn nữa: mặc dù dành nhiều giờ cố gắng triển khai chúng, tôi chưa bao giờ làm cho chúng hoạt động bình thường trên TanStack Start (tương tự như `gt-next` trên Next.js).

Các vấn đề gặp phải:

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

- Đối với một ứng dụng khoảng 110kb, `gt-react` có thể thêm vào hơn 440kb dữ liệu dư thừa (mức độ quan sát được trên triển khai Next.js trong cùng một benchmark).
- Thông báo `Quota Exceeded, please upgrade your plan` ngay trong lần xây dựng đầu tiên với General Translation.
- Các bản dịch không được render; tôi nhận được lỗi `Error: <T> used on the client-side outside of <GTProvider>`, dường như là một lỗi trong thư viện đó.
- Trong khi triển khai **gt-tanstack-start-react**, tôi cũng gặp phải một [vấn đề](https://github.com/generaltranslation/gt/issues/1210#event-24510646961) với thư viện: `does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser`, khiến ứng dụng bị hỏng. Sau khi báo cáo, người duy trì đã khắc phục nó trong vòng 24 giờ.
- Các thư viện này sử dụng một anti-pattern thông qua hàm `initializeGT()`, ngăn cản gói bundle loại bỏ mã thừa một cách sạch sẽ.

**(Lingo.dev)** (`lingo.dev@0.133.9`):

- Vượt quá hạn mức AI (hoặc chặn phụ thuộc server), khiến việc xây dựng / production gặp rủi ro nếu không trả tiền.
- Trình biên dịch đã bỏ sót gần 40% nội dung đã dịch. Tôi đã phải viết lại tất cả các cấu trúc `.map` thành các khối component phẳng để nó hoạt động.
- CLI của họ có nhiều lỗi và hay tự ý đặt lại tệp cấu hình không vì lý do gì.
- Khi xây dựng, nó đã xóa hoàn toàn các tệp JSON được tạo ra khi có nội dung mới được thêm vào. Kết quả là, bạn có thể kết thúc với việc chỉ một vài khóa xóa sạch hàng trăm khóa hiện có.
- Tôi đã gặp vấn đề về tính phản ứng với thư viện trên TanStack Start: khi ngôn ngữ thay đổi, tôi phải ép buộc render lại provider để nó hoạt động.

### 2 - Các giải pháp thử nghiệm

**(Wuchale)** (`wuchale@0.22.11`):

Ý tưởng đằng sau `Wuchale` rất thú vị nhưng vẫn chưa phải là một giải pháp khả thi. Tôi đã gặp vấn đề về tính phản ứng với thư viện này và phải ép buộc render lại provider để ứng dụng hoạt động trên TanStack Start. Tài liệu cũng khá mơ hồ, khiến việc làm quen khó khăn hơn.

### 3 - Các giải pháp chấp nhận được

**(Paraglide)** (`@inlang/paraglide-js@2.15.1`):

`Paraglide` mang đến một phương pháp tiếp cận sáng tạo và được cân nhắc kỹ lưỡng. Dù vậy, trong benchmark này, khả năng loại bỏ mã thừa mà công ty họ quảng cáo đã không hoạt động cho triển khai Next.js hoặc cho TanStack Start của tôi. Quy trình làm việc và trải nghiệm phát triển cũng phức tạp hơn các tùy chọn khác. Cá nhân tôi không phải là fan của việc phải tạo lại các tệp JS trước mỗi lần đẩy mã, điều này tạo ra rủi ro xung đột merge liên tục cho các nhà phát triển qua PR.

> Lưu ý về paraglide: giải pháp này đưa mã vào mã nguồn của bạn cho các lần nhập; kết quả là, chỉ số 'lib size' trong báo cáo benchmark gần như bằng 0. Việc tạo mã (Code generation) là một điều tốt, vì hàm được sử dụng sẽ chỉ bao gồm logic cần thiết (tiền tố ở mọi nơi so với không có tiền tố, cookie so với lưu trữ, v.v.). So với đó, Intlayer thực hiện việc lọc này thông qua việc đưa các biến môi trường vào bản dựng để buộc trình đóng gói (bundler) thực hiện tree-shake nội dung tùy thuộc vào logic. Nhờ đó, paraglide và intlayer cuối cùng là những giải pháp nhẹ hơn từ 6 đến 10 lần so with i18next hoặc next-intl.

**(Tolgee)** (`@tolgee/react@7.0.0`):

`Tolgee` giải quyết được nhiều vấn đề đã đề cập trước đó. Tôi thấy việc bắt đầu với Tolgee khó khăn hơn so với các công cụ khác có phương pháp tiếp cận tương tự. Nó không cung cấp tính an toàn kiểu dữ liệu, điều này cũng khiến việc phát hiện các khóa bị thiếu tại thời điểm biên dịch trở nên khó khăn hơn nhiều. Tôi đã phải bao bọc các API của Tolgee bằng các API của riêng mình để thêm tính năng phát hiện khóa bị thiếu.

Trên TanStack Start, tôi cũng gặp vấn đề về tính phản ứng: khi ngôn ngữ thay đổi, tôi phải ép buộc provider render lại và đăng ký vào các sự kiện thay đổi ngôn ngữ để việc tải ở một ngôn ngữ khác hoạt động chính xác.

**(use-intl)** (`use-intl@4.9.1`):

`use-intl` là phần "intl" hợp thời nhất trong hệ sinh thái React (cùng họ với `next-intl`) và thường được các trợ lý AI thúc đẩy, nhưng theo quan điểm của tôi thì điều đó là sai lầm trong một thiết lập ưu tiên hiệu năng. Bắt đầu khá đơn giản. Trong thực tế, quá trình tối ưu hóa và hạn chế rò rỉ khá phức tạp. Tương tự, việc kết hợp tải động + tạo namespace + các kiểu dữ liệu TypeScript làm chậm quá trình phát triển rất nhiều.

Trên TanStack Start, bạn tránh được các cạm bẫy đặc thù của Next.js (`setRequestLocale`, render tĩnh), nhưng vấn đề cốt lõi là như nhau: nếu không có kỷ luật nghiêm ngặt, gói bundle sẽ nhanh chóng mang theo quá nhiều thông điệp và việc bảo trì namespace cho từng route trở nên mệt mỏi.

**(react-i18next)** (`react-i18next@17.0.2`):

`react-i18next` có lẽ là tùy chọn phổ biến nhất vì nó là một trong những giải pháp đầu tiên phục vụ nhu cầu i18n cho các ứng dụng JavaScript. Nó cũng có một bộ plugin cộng đồng rộng lớn cho các vấn đề cụ thể.

Tuy nhiên, nó có cùng những nhược điểm chính như các ngăn công nghệ được xây dựng trên `t('a.b.c')`: tối ưu hóa là có thể nhưng rất tốn thời gian, và các dự án lớn có rủi ro rơi vào các thực hành xấu (namespace + tải động + kiểu dữ liệu).

Các định dạng thông báo cũng khác nhau: `use-intl` sử dụng ICU MessageFormat, trong khi `i18next` sử dụng định dạng riêng của mình - điều này làm phức tạp thêm công cụ hoặc quá trình di chuyển nếu bạn trộn lẫn chúng.

**(Lingui)** (`@lingui/core@5.3.0`):

`Lingui` thường được khen ngợi. Cá nhân tôi thấy quy trình làm việc xung quanh `lingui extract` / `lingui compile` phức tạp hơn các phương pháp tiếp cận khác, mà không có ưu điểm rõ ràng trong benchmark TanStack Start này. Tôi cũng nhận thấy cú pháp không nhất quán gây nhầm lẫn cho AI (ví dụ: `t()`, `t''`, `i18n.t()`, `<Trans>`).

**(react-intl)** (`react-intl@10.1.1`):

`react-intl` là một triển khai hiệu năng từ nhóm Format.js. Trải nghiệm phát triển vẫn còn rườm rà: `const intl = useIntl()` + `intl.formatMessage({ id: "xx.xx" })` thêm vào sự phức tạp, công việc JavaScript bổ sung và ràng buộc instance i18n toàn cục vào nhiều nút trong cây React.

### 4 - Các khuyến nghị

Benchmark TanStack Start này không có đối trọng trực tiếp cho `next-translate` (Next.js plugin + `getStaticProps`). Đối với các nhóm thực sự muốn một API `t()` với một hệ sinh thái chín muồi, `react-i18next` và `use-intl` vẫn là những lựa chọn "hợp lý", nhưng hãy chuẩn bị đầu tư nhiều thời gian tối ưu hóa để tránh rò rỉ.

**(Intlayer)** (`react-intlayer@8.7.12`):

Tôi sẽ không đích thân đánh giá `react-intlayer` vì tính khách quan, vì đây là giải pháp của riêng tôi.

### Ghi chú cá nhân

Ghi chú này mang tính cá nhân và không ảnh hưởng đến kết quả benchmark. Tuy nhiên, trong thế giới i18n, bạn thường thấy sự đồng thuận xung quanh một mẫu như `const t = useTranslation('xx')` + `<>{t('xx.xx')}</>` cho nội dung dịch.

Trong các ứng dụng React, việc đưa một hàm vào dưới dạng một `ReactNode`, theo quan điểm của tôi, là một anti-pattern. Nó cũng thêm vào sự phức tạp có thể tránh được và chi phí thực thi JavaScript (ngay cả khi hầu như không nhận thấy).
