---
createdAt: 2026-04-20
updatedAt: 2026-05-18
title: 2026년 Next.js를 위한 최고의 i18n 솔루션 - 벤치마크 리포트
description: next-intl, next-i18next, Intlayer와 같은 Next.js 국제화(i18n) 라이브러리를 비교합니다. 번들 크기, 누수, 반응성에 관한 상세 성능 리포트.
keywords:
  - benchmark
  - i18n
  - intl
  - nextjs
  - 성능
  - intlayer
slugs:
  - doc
  - benchmark
  - nextjs
author: Aymeric PINEAU
applicationTemplate: https://github.com/intlayer-org/benchmark-i18n
history:
  - version: 8.9.8
    date: 2026-05-18
    changes: "GitHub 스타 비교 추가"
  - version: 8.7.5
    date: 2026-01-06
    changes: "벤치마크 초기화"
---

# Next.js i18n 라이브러리 - 2026 벤치마크 리포트

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

## 목차

<Toc/>

## 인터랙티브 벤치마크

<I18nBenchmark framework="nextjs" vertical/>

## 결과 참조:

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

> [전체 벤치마크 데이터 보기](https://intlayer.org/markdown?url=https%3A%2F%2Fraw.githubusercontent.com%2Fintlayer-org%2Fbenchmark-i18n%2Fmain%2Freport%2Fscripts%2Fsummarize-nextjs.md)

전체 벤치마크 저장소는 [여기](https://github.com/intlayer-org/benchmark-i18n)에서 확인할 수 있습니다.

## 서론

국제화 라이브러리는 애플리케이션에 큰 영향을 미칩니다. 주요 위험은 사용자가 한 페이지만 방문할 때 모든 페이지와 모든 언어의 콘텐츠를 로드하는 것입니다.

앱이 성장함에 따라 번들 크기가 기하급수적으로 커질 수 있으며, 이는 성능 저하를 눈에 띄게 유발할 수 있습니다.

예를 들어, 가장 좋지 않은 사례의 경우 국제화 후 페이지 크기가 거의 4배까지 커질 수 있습니다.

i18n 라이브러리의 또 다른 영향은 개발 속도 저하입니다. 컴포넌트를 여러 언어를 지원하는 다국어 콘텐츠로 변환하는 작업은 시간이 많이 걸립니다.

이 문제는 해결하기 어렵기 때문에 DX에 초점을 맞춘 솔루션부터 성능이나 확장성에 초점을 맞춘 솔루션 등 다양한 대안이 존재합니다.

Intlayer는 이러한 모든 차원에서 최적화를 시도합니다.

## TL;DR

- **Intlayer** & **next-translate**: Next.js 성능을 위한 최선의 선택으로, 가장 작은 풋프린트와 최고의 정적 렌더링 지원을 제공합니다.
- **next-intl**: 가장 트렌디한 옵션이지만 대규모 애플리케이션을 위해 최적화하기에는 무겁고 복잡합니다.
- **next-i18next**: 인기가 많고 플러그인이 풍부하지만 상당한 번들 무게를 가집니다(Intlayer의 약 3배).
- **피해야 할 솔루션**: **gt-next** 및 **lingo.dev**는 심각한 성능 문제, 벤더 종속성 및 빌드를 중단시키는 버그로 인해 권장하지 않습니다.

## 앱 테스트하기

이러한 문제들을 파악하기 위해 [여기](https://intlayer.org/i18n-seo-scanner)에서 시도해 볼 수 있는 무료 스캐너를 구축했습니다.

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

## 문제점

다국어 앱이 번들에 미치는 영향을 제한하는 두 가지 주요 방법이 있습니다:

- 번들러가 특정 페이지에서 사용되지 않는 콘텐츠를 트리 쉐이킹(tree-shake)할 수 있도록 JSON(또는 콘텐츠)을 파일 / 변수 / 네임스페이스별로 분리합니다.
- 사용자의 언어로만 페이지 콘텐츠를 동적으로 로드합니다.

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

**동적 로딩**

Webpack이나 Turbopack을 사용하고 `generateStaticParams`가 정의되어 있더라도 `[locale]/page.tsx`와 같은 경로를 선언할 때 번들러는 `locale`을 정적 상수로 취급하지 않습니다. 즉, 모든 언어의 콘텐츠를 각 페이지로 끌어올 수 있음을 의미합니다. 이를 제한하는 주요 방법은 동적 임포트(예: `import('./locales/${locale}.json')`)를 통해 콘텐츠를 로드하는 것입니다.

빌드 시점에 발생하는 일은 Next.js가 로케일당 하나의 JS 번들(예: `./locales_fr_12345.js`)을 배출하는 것입니다. 사이트가 클라이언트에 전송된 후 페이지가 실행되면 브라우저는 필요한 JS 파일(예: `./locales_fr_12345.js`)에 대해 추가 HTTP 요청을 수행합니다.

> 같은 문제를 해결하는 또 다른 방법은 `fetch()`를 사용하여 JSON을 동적으로 로드하는 것입니다. 이는 JSON이 `/public`에 있는 경우 `Tolgee`가 작동하는 방식이거나, 콘텐츠 로딩을 위해 `getStaticProps`에 의존하는 `next-translate`가 작동하는 방식입니다. 흐름은 동일합니다: 브라우저는 애셋을 로드하기 위해 추가 HTTP 요청을 합니다.

**콘텐츠 분리**

`const t = useTranslation()` + `t('my-object.my-sub-object.my-key')`와 같은 구문을 사용하는 경우, 라이브러리가 키를 분석하고 해결할 수 있도록 일반적으로 전체 JSON이 번들에 포함되어야 합니다. 이로 인해 페이지에서 사용되지 않더라도 많은 콘텐츠가 함께 전송됩니다.

이를 완화하기 위해 일부 라이브러리는 페이지별로 로드할 네임스페이스를 선언하도록 요구합니다(예: `next-i18next`, `next-intl`, `lingui`, `next-translate`, `next-international`).

반면, `Paraglide`는 빌드 전에 JSON을 `const en_my_var = () => 'my value'`와 같은 평면적인 심볼로 변환하는 추가 단계를 추가합니다. 이론적으로 이는 페이지에서 사용되지 않는 콘텐츠의 트리 쉐이킹을 가능하게 합니다. 나중에 살펴보겠지만, 이 방법에도 장단점이 있습니다.

마지막으로, `Intlayer`는 빌드 시 최적화를 적용하여 `useIntlayer('my-key')`가 해당 콘텐츠로 직접 대체되도록 합니다.

## 연구 방법론

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

- `Base App` (i18n 라이브러리 없음)
- `next-intlayer` (v8.7.12)
- `next-i18next` (v16.0.5)
- `next-intl` (v4.9.1)
- `@lingui/core` (v5.3.0)
- `next-translate` (v3.1.2)
- `next-international` (v1.3.1)
- `@inlang/paraglide-js` (v2.15.1)
- `@tolgee/react` (v7.0.0)
- `@lingo.dev/compiler` (v0.4.0)
- `wuchale` (v0.22.11)
- `gt-next` (v6.16.5)

Next.js 버전 `16.2.4`와 App Router를 사용했습니다.

**10개의 페이지**와 **10개의 언어**를 가진 다국어 앱을 구축했습니다.

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

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

## 전략 요약

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

### 측정 항목:

각 스택에 대해 실제 브라우저에서 동일한 다국어 앱을 실행한 다음, 실제로 네트워크를 통해 전송된 데이터와 소요된 시간을 기록했습니다. 크기는 원본 소스 코드 수보다 사람들이 실제로 다운로드하는 것에 더 가까운 **일반적인 웹 압축 후** 크기로 보고됩니다.

- **국제화 라이브러리 크기**: 번들링, 트리 쉐이킹 및 미니피케이션 후의 i18n 라이브러리 크기입니다. 빈 컴포넌트 내의 프로바이더(예: `NextIntlClientProvider`) + 훅(예: `useTranslations`) 코드의 크기를 의미합니다. 여기에는 번역 파일의 로딩은 포함되지 않습니다. 콘텐츠가 들어가기 전 라이브러리 자체가 얼마나 무거운지에 대한 답을 줍니다.

- **페이지당 JavaScript**: 각 벤치마크 경로에 대해 브라우저가 해당 방문을 위해 가져오는 스크립트의 양으로, 스위트 내 페이지 전체(및 보고서에서 합산하는 경우 로케일 전체)의 평균값입니다. 무거운 페이지는 느린 페이지입니다.

- **다른 로케일에서의 누수**: 감시 대상 페이지에서 실수로 로드되는 동일한 페이지의 다른 언어 콘텐츠입니다. 이 콘텐츠는 불필요하며 피해야 합니다(예: `/en/about` 페이지 번들에 포함된 `/fr/about` 페이지 콘텐츠).

- **다른 경로에서의 누수**: 앱의 **다른 화면**에 대해서도 동일한 개념입니다. 한 페이지만 열었는데 다른 페이지의 텍스트가 함께 따라오는지 여부입니다(예: `/en/contact` 페이지 번들에 포함된 `/en/about` 페이지 콘텐츠). 높은 점수는 빈약한 분리 또는 지나치게 넓은 번들을 나타냅니다.

- **평균 컴포넌트 번들 크기**: 공통 UI 조각들을 하나의 거대한 앱 수치 안에 숨기는 대신 **하나씩** 측정합니다. 이는 국제화가 일상의 컴포넌트를 조용히 부풀리는지 여부를 보여줍니다. 예를 들어, 컴포넌트가 재렌더링되면 메모리에서 그 모든 데이터를 로드하게 됩니다. 거대한 JSON을 컴포넌트에 연결하는 것은 컴포넌트 성능을 저하시키는 사용되지 않는 데이터의 큰 저장소를 연결하는 것과 같습니다.

- **언어 전환 반응성**: 앱 자체 컨트롤을 사용하여 언어를 전환하고 페이지가 명확하게 전환될 때까지 걸리는 시간을 측정합니다. 실험실의 미세한 단계가 아닌 방문자가 체감하는 시간입니다.

- **언어 전환 후 렌더링 작업**: 좁은 범위의 후속 조치로, 전환이 진행된 후 인터페이스가 새 언어로 다시 그려지는 데 소요된 노력을 측정합니다. "체감" 시간과 프레임워크 비용이 다를 때 유용합니다.

- **초기 페이지 로드 시간**: 탐색부터 테스트한 시나리오에 대해 브라우저가 페이지를 완전히 로드한 것으로 간주할 때까지의 시간입니다. 콜드 스타트(cold starts)를 비교하는 데 좋습니다.

- **하이드레이션 시간**: 앱이 이를 노출할 때, 클라이언트가 서버 HTML을 실제로 클릭할 수 있는 것으로 전환하는 데 걸리는 시간입니다. 표의 대시(-)는 해당 구현이 이 벤치마크에서 신뢰할 수 있는 하이드레이션 수치를 제공하지 않았음을 의미합니다.

## GitHub 스타

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

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

## 결과 상세

### 1 - 피해야 할 솔루션

`gt-next`나 `lingo.dev`와 같은 일부 솔루션은 분명히 피하는 것이 좋습니다. 이들은 벤더 종속성(vendor lock-in)과 코드베이스 오염을 결합합니다. 이를 구현하기 위해 많은 시간을 투자했음에도 불구하고, TanStack Start나 Next.js에서 제대로 작동하지 않았습니다.

발생한 문제점들:

**(General Translation)** (`gt-next@6.16.5`):

- 110kb 앱의 경우 `gt-next`는 440kb 이상의 추가 데이터를 추가합니다.
- General Translation을 사용한 가장 첫 번째 빌드에서 `Quota Exceeded, please upgrade your plan` 메시지가 표시되었습니다.
- 번역이 렌더링되지 않습니다. `Error: <T> used on the client-side outside of <GTProvider>` 오류가 발생하며, 이는 라이브러리의 버그로 보입니다.
- **gt-next**를 구현하는 동안 라이브러리의 [이슈](https://github.com/generaltranslation/gt/issues/1210#event-24510646961)도 발견했습니다: `does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser` 오류로 애플리케이션이 중단되었습니다. 이 문제를 보고한 후 관리자는 24시간 이내에 수정했습니다.
- 이 라이브러리는 Next.js 페이지의 정적 렌더링을 차단합니다.

**(Lingo.dev)** (`@lingo.dev/compiler@0.4.0`):

- AI 쿼터 초과로 빌드가 완전히 차단되었습니다. 즉, 비용을 지불하지 않으면 프로덕션에 출시할 수 없습니다.
- 컴파일러가 번역된 콘텐츠의 거의 40%를 놓치고 있었습니다. 작동시키기 위해 모든 `.map`을 평면적인 컴포넌트 블록으로 다시 작성해야 했습니다.
- CLI에 버그가 있어 아무 이유 없이 설정 파일을 초기화하곤 했습니다.
- 빌드 시 새 콘텐츠가 추가되면 생성된 JSON을 완전히 삭제했습니다. 결과적으로 소수의 키가 300개 이상의 기존 키를 지워버릴 수 있었습니다.

### 2 - 실험적인 솔루션

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

`Wuchale`의 아이디어는 흥미롭지만 아직 실행 가능하지 않습니다. 반응성 문제에 부딪혔고 앱을 작동시키기 위해 프로바이더의 강제 재렌더링이 필요했습니다. 문서 또한 상당히 불명확하여 온보딩이 어렵습니다.

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

`Paraglide`는 혁신적이고 잘 설계된 접근 방식을 제공합니다. 그럼에도 불구하고 이 벤치마크에서는 광고된 트리 쉐이킹이 나의 Next.js 또는 TanStack Start 설정에서 작동하지 않았습니다. 워크플로우와 DX는 다른 옵션보다 복잡합니다.
개인적으로 푸시할 때마다 JS 파일을 다시 생성해야 하는 것이 싫습니다. 이는 PR을 통한 지속적인 머지 충돌 위험을 만듭니다. 또한 이 도구는 Next.js보다 Vite에 더 집중하는 것으로 보입니다.
마지막으로, 다른 솔루션과 비교할 때 Paraglide는 콘텐츠를 렌더링하기 위해 현재 로케일을 조회하는 스토어(예: React context)를 사용하지 않습니다. 파싱되는 각 노드에 대해 localStorage / 쿠키 등에서 로케일을 요청합니다. 이는 컴포넌트 반응성에 영향을 주는 불필요한 로직 실행으로 이어집니다.

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

### 3 - 수용 가능한 솔루션

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

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

**(Next Intl)** (`next-intl@4.9.1`):

`next-intl`은 가장 유행하는 옵션이며 AI 에이전트들이 가장 많이 추천하는 옵션이지만, 내 견해로는 성능 면에서 잘못된 추천입니다. 시작하기는 쉽습니다. 실제로는 누수를 제한하기 위한 최적화가 복잡합니다. 동적 로딩 + 네임스페이싱 + TypeScript 타입을 결합하면 개발 속도가 크게 느려집니다. 패키지 또한 상당히 무겁습니다(`NextIntlClientProvider` + `useTranslations`의 경우 약 13kb로, `next-intlayer`의 2배 이상입니다). **next-intl**은 Next.js 페이지의 정적 렌더링을 차단하곤 했습니다. `setRequestLocale()`이라는 헬퍼를 제공합니다. `en.json` / `fr.json`과 같은 중앙 집중식 파일의 경우 일부 해결된 것으로 보이지만, 콘텐츠가 `en/shared.json` / `fr/shared.json` / `es/shared.json`과 같은 네임스페이스로 분리된 경우 정적 렌더링이 여전히 깨집니다.

**(Next I18next)** (`next-i18next@16.0.5`):

`next-i18next`는 JavaScript 앱 i18n 솔루션 중 초기에 등장했기 때문에 가장 인기 있는 옵션일 것입니다. 많은 커뮤니티 플러그인을 보유하고 있습니다. `next-intl`과 같은 주요 단점을 공유합니다. 패키지가 특히 무겁습니다(`I18nProvider` + `useTranslation`의 경우 약 18kb로, `next-intlayer`의 약 3배입니다).

메시지 형식도 다릅니다: `next-intl`은 ICU MessageFormat을 사용하는 반면, `i18next`는 자체 형식을 사용합니다.

**(Next International)** (`next-international@1.3.1`):

`next-international` 또한 위의 문제들을 다루지만 `next-intl`이나 `next-i18next`와 크게 다르지 않습니다. 네임스페이스별 번역을 위한 `scopedT()`를 포함하지만, 이를 사용해도 번들 크기에는 거의 영향이 없습니다.

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

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

### 4 - 추천 사항

**(Next Translate)** (`next-translate@3.1.2`):

`t()` 스타일의 API를 선호한다면 `next-translate`가 주요 추천 사항입니다. Webpack / Turbopack 로더를 통해 `getStaticProps`에서 네임스페이스를 로드하는 `next-translate-plugin`을 통해 우아하게 작동합니다. 또한 여기에서 가장 가벼운 옵션입니다(약 2.5kb). 네임스페이싱의 경우 설정 파일에서 페이지 또는 경로별 네임스페이스를 정의하는 방식이 잘 생각되어 있으며 **next-intl**이나 **next-i18next**와 같은 주요 대안보다 유지 관리가 쉽습니다. 버전 `3.1.2`에서 정적 렌더링이 작동하지 않았고 Next.js가 동적 렌더링으로 폴백(fallback)되는 것을 확인했습니다.

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

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

### 개인적인 의견

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

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