작가:
    생성:2026-04-20마지막 업데이트:2026-05-18

    TanStack Start i18n 라이브러리 - 2026 벤치마크 리포트

    이 페이지는 TanStack Start i18n 솔루션에 대한 벤치마크 리포트입니다.

    목차

    인터랙티브 벤치마크

    결과 참조:

    intlayer.org
    전체 벤치마크 데이터 보기

    전체 벤치마크 저장소는 여기에서 확인할 수 있습니다.

    서론

    국제화 솔루션은 React 앱에서 가장 무거운 의존성 중 하나입니다. TanStack Start에서 주요 위험은 불필요한 콘텐츠, 즉 단일 경로의 번들에 다른 페이지와 다른 로케일의 번역을 포함하는 것입니다.

    앱이 성장함에 따라 이 문제는 클라이언트에 전송되는 JavaScript를 빠르게 팽창시키고 내비게이션을 느리게 만들 수 있습니다.

    실제로 최적화가 가장 덜 된 구현의 경우, 국제화된 페이지가 i18n이 없는 버전보다 몇 배나 무거워질 수 있습니다.

    또 다른 영향은 개발자 경험(DX)입니다: 콘텐츠 선언 방식, 타입, 네임스페이스 구성, 동적 로딩, 로케일 변경 시의 반응성 등이 포함됩니다.

    TL;DR

    • Intlayer: TanStack Start에서 최고의 성능과 가장 작은 번들 크(v8.7.12)를 제공합니다.
    • react-i18next & use-intl: 큰 에코시스템을 가진 성숙한 대안이지만, 훨씬 더 무겁고 최적화가 복잡합니다.
    • Paraglide: 혁신적인 트리 쉐이킹 아이디어이지만 실제로는 작동하지 않습니다. TanStack Start에서 복잡한 DX와 반응성 오버헤드가 발생합니다.
    • 피해야 할 솔루션: General Translation (GT)Lingo.dev. 심각한 성능 문제, AI 쿼터 제한 및 벤더 종속성(vendor lock-in) 때문입니다.

    앱 테스트하기

    i18n 누수 문제를 빠르게 파악하기 위해 여기에서 시도해 볼 수 있는 무료 스캐너를 구축했습니다.

    intlayer.org

    문제점

    다국어 앱의 비용을 제한하려면 두 가지 레버가 필수적입니다:

    • 페이지 / 네임스페이스별로 콘텐츠를 분리하여 필요하지 않을 때 전체 사전을 로드하지 않도록 합니다.
    • 필요할 때만 올바른 로케일을 동적으로 로드합니다.

    이러한 접근 방식의 기술적 한계 이해:

    동적 로딩

    동적 로딩이 없으면 대부분의 솔루션은 첫 번째 렌더링부터 메시지를 메모리에 유지하므로 경로와 로케일이 많은 앱의 경우 상당한 오버헤드가 발생합니다.

    동적 로딩을 사용하면 초기 JS는 줄어들지만 언어 전환 시 추가 요청이 발생할 수 있는 장단점을 수용하게 됩니다.

    콘텐츠 분리

    const t = useTranslation() + t('a.b.c')를 중심으로 구축된 구문은 매우 편리하지만 런타임에 큰 JSON 객체를 유지하도록 조장하는 경우가 많습니다. 이 모델은 라이브러리가 실제 페이지별 분리 전략을 제공하지 않는 한 트리 쉐이킹을 어렵게 만듭니다.

    연구 방법론

    이 벤치마크에서는 다음과 같은 라이브러리를 비교했습니다:

    • Base App (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)

    프레임워크는 TanStack Start이며 10개의 페이지10개의 언어를 가진 다국어 앱을 사용했습니다.

    네 가지 로딩 전략을 비교했습니다:

    전략 네임스페이스 없음 (글로벌) 네임스페이스 포함 (스코프)
    정적 로딩 Static: 시작 시 모든 것을 메모리에 로드. Scoped static: 네임스페이스별 분리; 시작 시 모든 것을 로드.
    동적 로딩 Dynamic: 로케일별 온디맨드 로딩. Scoped dynamic: 네임스페이스 및 로케일별 세분화된 로딩.

    전략 요약

    • Static: 간단함. 초기 로드 후 네트워크 지연 없음. 단점: 큰 번들 크기.
    • Dynamic: 초기 무게 감소(지연 로딩). 로케일이 많을 때 적합함.
    • Scoped static: 복잡한 추가 네트워크 요청 없이 코드 구조를 유지(논리적 분리).
    • Scoped dynamic: 코드 분할 및 성능을 위한 최선의 접근 방식. 현재 뷰와 활성 로케일에 필요한 것만 로드하여 메모리 사용을 최소화함.

    측정 항목:

    모든 스택에 대해 실제 브라우저에서 동일한 다국어 앱을 실행하고, 실제로 전송되는 내용과 소요 시간을 기록했습니다. 크기는 일반적인 웹 압축 후에 보고되며, 이는 원본 소스 크기보다 사용자가 다운로드하는 내용에 더 가깝습니다.

    • Internationalization 라이브러리 크기: 번들링, 트리-셰이킹 및 최소화 후, i18n 라이브러리의 크기는 프로바이더(예: NextIntlClientProvider) + 훅(예: useTranslations) 코드의 크기를 빈 컴포넌트에서 측정한 것입니다. 번역 파일 로딩은 포함되지 않습니다. 콘텐츠가 들어오기 전에 라이브러리가 얼마나 비용이 드는지를 답합니다.

    • 페이지당 JavaScript: 각 벤치마크 경로에 대해 방문 시 브라우저가 가져오는 스크립트의 양으로, 스위트의 페이지 전체에서 평균화됩니다(리포트가 롤업하는 경우 로케일 전체에서). 무거운 페이지는 느린 페이지입니다.

    • 다른 로케일에서의 유출: 감사된 페이지에 실수로 로드될 수 있는 다른 언어의 동일한 페이지 콘텐츠입니다. 이 콘텐츠는 불필요하며 피해야 합니다. (예: /en/about 페이지 번들에서 /fr/about 페이지 콘텐츠)

    • 다른 경로에서의 유출: 다른 화면의 콘텐츠가 한 페이지만 열었을 때 함께 로드되는지 여부에 대한 동일한 개념입니다. (예: /en/contact 페이지 번들에서 /en/about 페이지 콘텐츠). 높은 점수는 약한 분할 또는 과도하게 넓은 번들을 시사합니다.

    • 평균 컴포넌트 번들 크기: 일반적인 UI 조각들은 한 번에 하나씩 측정되며, 거대한 앱 번호 안에 숨겨지지 않습니다. internationalization이 일상적인 컴포넌트를 조용히 부풀리는지 여부를 보여줍니다. 예를 들어, 컴포넌트가 다시 렌더링되면 메모리에서 모든 데이터를 로드하게 됩니다. 거대한 JSON을 컴포넌트에 연결하는 것은 컴포넌트의 성능을 저하시킬 수 있는 사용되지 않은 데이터의 큰 저장소에 연결하는 것과 같습니다.

    • 언어 전환 반응성: 앱의 자체 컨트롤을 사용하여 언어를 전환하고 페이지가 명확하게 전환될 때까지의 소요 시간을 측정합니다. 방문자가 알 수 있는 수준이며, 실험실 마이크로 단계가 아닙니다.

    • 언어 변경 후 렌더링 작업: 더 좁은 후속 작업으로, 전환이 진행 중일 때 새 언어를 다시 칠하기 위해 인터페이스가 소비한 노력입니다. "느껴지는" 시간과 프레임워크 비용이 다를 때 유용합니다.

    • 초기 페이지 로드 시간: 탐색에서 브라우저가 테스트한 시나리오에 대해 페이지가 완전히 로드되었다고 간주할 때까지의 시간입니다. 콜드 스타트를 비교하는 데 좋습니다.

    • 수화 시간: 앱이 노출할 때, 클라이언트가 서버 HTML을 실제로 클릭할 수 있는 것으로 변환하는 데 소비하는 시간입니다. 테이블의 대시는 해당 구현이 이 벤치마크에서 신뢰할 수 있는 수화 수치를 제공하지 않았음을 의미합니다.

    GitHub 스타

    GitHub 스타는 프로젝트의 인기, 커뮤니티 신뢰 및 장기적인 관련성을 나타내는 강력한 지표입니다. 기술적 품질을 직접적으로 측정하는 것은 아니지만, 얼마나 많은 개발자가 프로젝트가 유용하다고 생각하고 진행 상황을 팔로우하며 채택할 가능성이 있는지를 반영합니다. 프로젝트의 가치를 평가할 때 스타는 대안 간의 견인력을 비교하는 데 도움이 되며 생태계 성장에 대한 통찰력을 제공합니다.

    Star History Chart

    결과 상세

    1 - 피해야 할 솔루션

    gt-reactlingo.dev와 같은 일부 솔루션은 분명히 피해야 합니다. 이들은 벤더 종속성과 코드베이스 오염을 결합합니다. 설상가상으로 이를 구현하기 위해 많은 시간을 투자했음에도 불구하고 TanStack Start에서 제대로 작동하지 않았습니다(Next.js의 gt-next와 유사).

    발생한 문제점들:

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

    • 약 110kb 앱의 경우 gt-react는 440kb 이상의 추가 데이터를 추가할 수 있습니다(동일 벤치마크의 Next.js 구현에서 확인된 규모).
    • General Translation을 사용한 가장 첫 번째 빌드에서 Quota Exceeded, please upgrade your plan 메시지가 표시되었습니다.
    • 번역이 렌더링되지 않습니다. Error: <T> used on the client-side outside of <GTProvider> 오류가 발생하며, 이는 라이브러리의 버그로 보입니다.
    • gt-tanstack-start-react를 구현하는 동안 라이브러리의 이슈도 발견했습니다: does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser 오류로 애플리케이션이 중단되었습니다. 이 문제를 보고한 후 관리자는 24시간 이내에 수정했습니다.
    • 이 라이브러리들은 initializeGT() 함수를 통해 안티 패턴을 사용하여 번들이 깨끗하게 트리 쉐이킹되는 것을 방해합니다.

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

    • AI 쿼터 초과(또는 차단 중인 서버 의존성)로 인해 비용을 지불하지 않으면 빌드 / 프로덕션 출시가 위험해집니다.
    • 컴파일러가 번역된 콘텐츠의 거의 40%를 놓치고 있었습니다. 작동시키기 위해 모든 .map을 평면적인 컴포넌트 블록으로 다시 작성해야 했습니다.
    • CLI에 버그가 있어 아무 이유 없이 설정 파일을 초기화하곤 했습니다.
    • 빌드 시 새 콘텐츠가 추가되면 생성된 JSON을 완전히 삭제했습니다. 결과적으로 소수의 키가 수백 개의 기존 키를 지워버릴 수 있었습니다.
    • TanStack Start에서 라이브러리의 반응성 문제를 겪었습니다: 로케일 변경 시 작동시키기 위해 프로바이더의 강제 재렌더링이 필요했습니다.

    2 - 실험적인 솔루션

    (Wuchale) (wuchale@0.22.11):

    Wuchale의 아이디어는 흥미롭지만 아직 실행 가능한 솔루션은 아닙니다. 이 라이브러리에서 반응성 문제를 겪었으며 TanStack Start에서 앱을 작동시키기 위해 프로바이더의 강제 재렌더링이 필요했습니다. 문서 또한 상당히 불명확하여 온보딩이 어렵습니다.

    3 - 수용 가능한 솔루션

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

    Paraglide는 혁신적이고 잘 설계된 접근 방식을 제공합니다. 그럼에도 불구하고 이 벤치마크에서는 광고된 트리 쉐이킹이 나의 Next.js 구현이나 TanStack Start에서 작동하지 않았습니다. 워크플로우와 DX 또한 다른 옵션보다 복잡합니다. 개인적으로 푸시할 때마다 JS 파일을 다시 생성해야 하는 것을 선호하지 않으며, 이는 PR을 통한 개발자들 간의 지속적인 머지 충돌 위험을 만듭니다.

    paraglide에 관한 참고: 이 솔루션은 임포트를 위해 코드베이스에 코드를 주입하므로, 벤치마크 리포트의 'lib size' 메트릭은 거의 0에 가깝습니다. 코드 생성(Code generation)은 사용되는 함수에 필요한 로직(모든 접두사 vs 접두사 없음, 쿠키 vs 스토리지 등)만 포함되므로 좋은 방식입니다. 이에 비해 Intlayer는 빌드 시 환경 변수 주입을 통해 이 필터링을 수행하여 번들러가 로직에 따라 콘텐츠를 트리 쉐이킹하도록 강제합니다. 덕분에 paraglide와 intlayer는 i18next나 next-intl보다 6~10배 더 가벼운 솔루션이 됩니다.

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

    Tolgee는 앞에서 언급한 많은 문제들을 해결합니다. 비슷한 접근 방식을 가진 다른 도구들보다 시작하기 더 어렵다고 느꼈습니다. 타입 안전성을 제공하지 않아 컴파일 시점에 누락된 키를 찾는 것도 매우 어렵습니다. 누락된 키 감지 기능을 추가하기 위해 Tolgee의 API를 나의 API로 래핑해야 했습니다.

    TanStack Start에서도 반응성 문제가 있었습니다: 로케일 변경 시 프로바이더를 강제 재렌더링하고 로케일 변경 이벤트를 구독하여 다른 언어로의 로딩이 올바르게 작동하도록 해야 했습니다.

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

    use-intl은 React 에코시스템에서 가장 유행하는 "intl" 조각 중 하나이며(next-intl과 같은 계열) AI 에이전트들에 의해 자주 추천되지만, 내 견해로는 성능 우선 환경에서 잘못된 선택입니다. 시작하기는 꽤 간단합니다. 실제로는 누수를 최적화하고 제한하는 프로세스가 상당히 복잡합니다. 마찬가지로 동적 로딩 + 네임스페이싱 + TypeScript 타입을 결합하면 개발 속도가 크게 느려집니다.

    TanStack Start에서는 Next.js 전용 함정(setRequestLocale, 정적 렌더링)을 피할 수 있지만 핵심 문제는 동일합니다: 엄격한 규율이 없으면 번들은 금세 너무 많은 메시지를 포함하게 되고 경로별 네임스페이스 유지는 고통스러워집니다.

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

    react-i18next는 JavaScript 앱 i18n 요구 사항을 충족시킨 초창기 솔루션 중 하나였기 때문에 가장 인기 있는 옵션일 것입니다. 또한 특정 문제를 해결하기 위한 광범위한 커뮤니티 플러그인 세트를 보유하고 있습니다.

    여전히 t('a.b.c') 위에 구축된 스택과 동일한 주요 단점을 공유합니다: 최적화는 가능하지만 시간이 매우 많이 소요되며, 대규모 프로젝트는 나쁜 관행(네임스페이스 + 동적 로딩 + 타입)에 빠질 위험이 있습니다.

    메시지 형식도 다릅니다: use-intl은 ICU MessageFormat을 사용하는 반면, i18next는 자체 형식을 사용하므로 이들을 섞어서 사용하면 도구 기술이나 마이그레이션이 복잡해집니다.

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

    Lingui는 종종 찬사를 받습니다. 개인적으로 lingui extract / lingui compile 워크플로우가 다른 접근 방식보다 복잡하다고 느꼈으며 이 TanStack Start 벤치마크에서 명확한 장점이 없었습니다. 또한 AI를 혼란스럽게 하는 일관성 없는 구문(예: t(), t'', i18n.t(), <Trans>)을 발견했습니다.

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

    react-intl은 Format.js 팀의 성능 중심 구현입니다. DX는 여전히 장황합니다: const intl = useIntl() + intl.formatMessage({ id: "xx.xx" })는 복잡성과 추가 JavaScript 작업을 가중시키고 글로벌 i18n 인스턴스를 React 트리의 많은 노드에 묶어둡니다.

    4 - 추천 사항

    이 TanStack Start 벤치마크에는 next-translate(Next.js 플러그인 + getStaticProps)에 상응하는 직접적인 대안이 없습니다. 성숙한 에코시스템과 t() API를 진정으로 원하는 팀에게는 react-i18nextuse-intl이 "합리적인" 선택으로 남겠지만, 누수를 피하기 위해 최적화하는 데 많은 시간을 투자할 것을 각오해야 합니다.

    (Intlayer) (react-intlayer@8.7.12):

    객관성을 위해 나의 솔루션인 react-intlayer에 대해서는 직접 판단하지 않겠습니다.

    개인적인 의견

    이 의견은 개인적인 것이며 벤치마크 결과에는 영향을 미치지 않습니다. 그럼에도 불구하고 i18n 세계에서는 번역된 콘텐츠를 위해 const t = useTranslation('xx') + <>{t('xx.xx')}</>와 같은 패턴에 대한 합의가 자주 보입니다.

    React 앱에서 함수를 ReactNode로 주입하는 것은 내 견해로는 안티 패턴입니다. 또한 피할 수 있는 복잡성과 JavaScript 실행 오버헤드(거의 눈에 띄지 않더라도)를 추가합니다.