Faça sua pergunta e obtenha um resumo do documento referenciando esta página e o provedor AI de sua escolha
O conteúdo desta página foi traduzido com uma IA.
Veja a última versão do conteúdo original em inglêsSe você tiver uma ideia para melhorar esta documentação, sinta-se à vontade para contribuir enviando uma pull request no GitHub.
Link do GitHub para a documentaçãoCopiar o Markdown do documento para a área de transferência
next-intl VS Intlayer | Benchmark de Internacionalização (i18n) do Next.js
next-intl é a biblioteca i18n mais popular para Next.js. Intlayer é uma alternativa baseada em compilador e com escopo de componente. Ambas localizam uma aplicação App Router. A questão é quanto cada uma custa uma vez que a aplicação está compilada.
Este artigo não é um tutorial. É uma comparação apoiada por números do Benchmark Bloom, um conjunto de benchmark open-source que constrói a mesma aplicação com cada biblioteca e mede o que o navegador realmente faz download e executa.
tl;dr: Na mesma aplicação Next.js,next-intladiciona +12.6 KB gzip de JavaScript em cada página, versus +0.3 KB para Intlayer. Sem trabalho extra,next-intlenvia ~90% das strings de páginas estrangeiras com cada página. Alcançar 0% de vazamento comnext-intlrequer escopo de namespace epick(messages, [...])por página. Intlayer alcança 0% por padrão, porque seu compiler escopeia o conteúdo por componente. Se você quer a APInext-intlcom a saída do Intlayer, o adaptador@intlayer/next-intlmediu 147.5 KB por página versus 153.6 KB com o original.
Em resumo
- next-intl - Leve, bem documentado, formato de mensagem ICU, suporte de primeira classe ao App Router com middleware, formatters e helpers de navegação. O conteúdo reside em catálogos JSON centralizados; otimizações de desempenho (namespaces, seleção de mensagens por página, lazy loading) são sua responsabilidade.
- Intlayer - Modelo de conteúdo centrado em componentes. Dicionários
.content.tsficam ao lado do componente que servem, um compilador em tempo de build faz tree-shake e lazy-loads deles por componente e por locale, tipos TypeScript rigorosos são gerados a partir do seu conteúdo, e traduções ausentes falham em tempo de build. Inclui middleware, helpers de SEO, um Visual Editor / CMS e tradução assistida por IA.
Abrir a tabela em um modal para ver todo o conteúdo claramente
Os emblemas são atualizados automaticamente. Os snapshots variarão ao longo do tempo.
Comparação de recursos lado a lado
Abrir a tabela em um modal para ver todo o conteúdo claramente
| Recurso | next-intlayer (Intlayer) | next-intl |
|---|---|---|
| Traduções próximas aos componentes | ✅ Sim, .content.ts colocado junto com cada componente | ❌ Não, centralizado em messages/{locale}.json |
| Integração TypeScript | ✅ Tipos estritos gerados automaticamente a partir do conteúdo | ✅ Bom, chaves tipadas via augmentação global.d.ts |
| Detecção de tradução ausente | ✅ Erro TypeScript + erro/aviso em tempo de build | ⚠️ Fallback em tempo de execução + aviso no console |
| Conteúdo rico (JSX / Markdown / componentes) | ✅ Suporte direto | ⚠️ t.rich() / t.markup() com placeholders de tag |
| Suporte ICU | ⚠️ Em desenvolvimento | ✅ Sim |
| Formatação (datas, números, moedas) | ✅ useNumber, useDate, ... (Intl sob o capô) | ✅ useFormatter() (Intl sob o capô) |
| Roteamento localizado e middleware | ✅ Proxy/middleware integrado, getMultilingualUrls | ✅ Middleware integrado, Link, redirect, usePathname |
| Auxiliares de SEO (hreflang, sitemap, robots) | ✅ Auxiliares integrados | ⚠️ Manual, baseado na configuração de roteamento |
| Componentes de servidor síncrono | ✅ useIntlayer de next-intlayer/server funciona em qualquer componente servidor filho | ⚠️ getTranslations é assíncrono; filhos síncronos precisam de t passado como props |
| Renderização estática | ✅ Não bloqueia a renderização estática | ⚠️ Requer setRequestLocale(); catálogos nomeados ainda optaram páginas fora da renderização estática em nossos testes |
| Tree-shaking (enviar apenas conteúdo usado) | ✅ Por componente, por locale, automatizado pelo compilador | ⚠️ Manual: namespaces + pick(messages, [...]) por página |
| Lazy loading | ✅ importMode: 'dynamic' (uma linha de config) | ⚠️ Importações dinâmicas manuais em getRequestConfig |
| Purge unused content | ✅ Dicionários não utilizados são removidos no momento da compilação | ❌ Não incluído |
| Testing missing translations (CLI / CI) | ✅ npx intlayer content test | ⚠️ Não incluído; docs sugerem npx @lingual/i18n-check |
| AI-powered translation | ✅ Incluído, usa suas próprias chaves de provedor | ❌ Não |
| Editor Visual / CMS | ✅ Editor Visual gratuito + CMS opcional | ❌ Não (plataformas de localização externas) |
| Servidor MCP & Agent Skills | ✅ Sim | ❌ Não |
| Ecossistema / comunidade | ⚠️ Menor mas crescendo rapidamente | ✅ Grande, a referência do Next.js |
O benchmark
O que foi medido
O suite Benchmark Bloom constrói a mesma aplicação com cada biblioteca: 10 páginas (home, about, blog, careers, contact, FAQ, pricing, products, settings, team), 10 locales (en, fr, es, de, it, pt, zh, ja, ko, ru), componentes idênticos e conteúdo idêntico. As páginas são medidas em en e fr. Cada biblioteca é implementada em até quatro estratégias de carregamento, da configuração ingênua até a otimizada:
Abrir a tabela em um modal para ver todo o conteúdo claramente
| Strategy | Description | Who does this |
|---|---|---|
| static | Cada locale e cada página agrupadas juntas | Protótipos rápidos, código gerado por IA |
| dynamic | Apenas a locale ativa é carregada, mas todas as páginas de uma vez | A maioria dos projetos |
| scoped-static | Namespaces por rota, sem lazy loading | Raro |
| scoped-dynamic | Namespaces por rota + lazy loading. Apenas a página atual na locale atual é enviada | Apps com orçamento de performance rigoroso |
Intlayer não possui uma variante "scoped": o compilador agrupa conteúdo por componente automaticamente, então suas linhas static e dynamic já estão agrupadas.
Para cada build, a suite registra:
- Lib size: tamanho gzip da biblioteca i18n. O custo fixo do runtime.
- Page JS: JavaScript gzip baixado por página, calculado em média sobre todas as páginas e locales.
- Locale leak %: proporção de strings traduzidas encontradas no JS baixado que pertencem a um locale que o usuário não está visualizando (fingerprinted em
enefr, então 50% significa "o outro locale medido está totalmente presente"; com 10 locales agrupados, o desperdício real é maior). - Page leak %: proporção de strings traduzidas encontradas no JS baixado que pertencem a uma página em que o usuário não está.
- Component avg: tamanho gzip médio de cada componente compilado isoladamente. Mostra quanto runtime i18n um único componente carrega.
- E2E reactivity: tempo real entre selecionar uma nova locale e
html[lang]atualizar no DOM (Playwright, 5 iterações). - Hydration: duração da fase de hidratação do React.
Os números abaixo vêm da execução datada de 2026-09-12 comnext-intl4.14.2,use-intl4.14.2 eintlayer9.5.1. A aplicação de teste é deliberadamente pequena (algumas dezenas de strings por locale), portanto as porcentagens de vazamento descrevem um padrão: eles crescem com seu conteúdo enquanto o custo de runtime permanece fixo.
Resultados no Next.js (App Router)
Abrir a tabela em um modal para ver todo o conteúdo claramente
| Library | Strategy | Lib size (gz) | Page JS avg (gz) | Locale leak | Page leak | Component avg (gz) | E2E reactivity | Hydration |
|---|---|---|---|---|---|---|---|---|
| base (sem i18n) | - | 0.0 KB | 141.0 KB | 0.0% | 0.0% | 0.9 KB | 13.4 ms | 11.8 ms |
next-intl | static | 14.7 KB | 153.6 KB | 4.2% | 89.8% | 21.8 KB | 16.0 ms | 14.7 ms |
next-intl | dynamic | 14.7 KB | 153.6 KB | 9.7% | 89.9% | 21.8 KB | 15.6 ms | 14.8 ms |
next-intl | scoped-static | 14.7 KB | 153.6 KB | 0.0% | 0.0% | 80.1 KB | 17.9 ms | 17.4 ms |
next-intl | scoped-dynamic | 14.7 KB | 153.6 KB | 0.0% | 0.0% | 22.9 KB | 17.8 ms | 16.8 ms |
next-intlayer | static | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 8.5 KB | 15.5 ms | 16.9 ms |
next-intlayer | dynamic | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 6.9 KB | 15.3 ms | 15.9 ms |
@intlayer/next-intl (compat) | static | 8.0 KB | 147.5 KB | 0.0% | 0.0% | 8.1 KB | 14.5 ms | 12.8 ms |
@intlayer/next-intl (compat) | dynamic | 8.0 KB | 148.7 KB | 0.0% | 0.0% | 8.1 KB | 11.7 ms | 12.8 ms |
Como ler isso
- Custo de runtime. A aplicação base pesa 141.0 KB por página.
next-intla leva para 153.6 KB (+12.6 KB gzip em cada página), Intlayer para 141.3 KB (+0.3 KB). Esta diferença não depende de quantas strings você tem: é o runtime da biblioteca. - Vazamento. Nos dois setups que a maioria das equipes realmente implementa (
staticedynamic),next-intlentrega ~90% das strings de páginas estrangeiras com cada página: todo oen.jsonvai para o provider do cliente. Para chegar a 0% é necessário os setupsscoped-*: dividir catálogos em namespaces e depois fazerpick()dos corretos em cada página. Intlayer está em 0% em ambas as linhas sem nada disso. - O JS por página não se moveu para
next-intlentre estratégias. O conteúdo do teste é pequeno, então o vazamento de ~90% é apenas alguns KB aqui. Em uma app real com centenas de strings por página, essa proporção se torna o custo dominante. Enquanto isso, o runtime de +12.6 KB é pago em cada configuração. - Tamanho do componente. Um componente que chama
useTranslations()compila para 21.8 KB em média; o mesmo componente comuseIntlayer()compila para 6.9 KB. Na configuraçãoscoped-static, os componentesnext-intlsaltam para 80.1 KB porque cada um internaliza seu catálogo de namespace. - Reatividade e hidratação estão no mesmo patamar para ambas as bibliotecas no Next.js (15-18 ms). Nenhuma delas é um gargalo aqui.
Resultados no TanStack Start (use-intl)
use-intl é o núcleo agnóstico de framework do next-intl. Mesma API, mesmo formato de mensagem. Comparar isso contra intlayer no TanStack Start remove as partes específicas do Next.js da equação.
Abrir a tabela em um modal para ver todo o conteúdo claramente
| Library | Strategy | Lib size (gz) | Page JS avg (gz) | Locale leak | Page leak | Component avg (gz) | E2E reactivity |
|---|---|---|---|---|---|---|---|
| base (sem i18n) | - | 0.0 KB | 111.0 KB | 0.0% | 0.0% | 0.7 KB | 8.1 ms |
use-intl | static | 14.1 KB | 179.8 KB | 50.0% | 89.8% | 76.0 KB | 6.7 ms |
use-intl | dynamic | 14.1 KB | 119.4 KB | 0.0% | 89.8% | 75.9 KB | 7.0 ms |
use-intl | scoped-static | 14.1 KB | 128.7 KB | 0.0% | 0.0% | 87.1 KB | 20.9 ms |
use-intl | scoped-dynamic | 14.1 KB | 128.7 KB | 0.0% | 0.0% | 87.1 KB | 13.3 ms |
intlayer | static | 5.0 KB | 125.8 KB | 50.0% | 0.0% | 8.1 KB | 3.2 ms |
intlayer | dynamic | 5.0 KB | 118.6 KB | 0.0% | 0.0% | 6.3 KB | 3.6 ms |
@intlayer/use-intl (compat) | dynamic | 7.3 KB | 129.7 KB | 0.0% | 0.0% | 9.3 KB | 8.7 ms |
Como ler isso
- A configuração ingênua
use-intlenvia 68.8 KB a mais de JS por página do que a aplicação base, com metade das strings pertencendo à locale errada e 90% à página errada. use-intlem mododynamicchega a 119.4 KB, próximo ao 118.6 KB do Intlayer, mas ainda carrega 89.8% de vazamento de página: as strings de todas as páginas para a locale ativa são carregadas em cada página. Escopo-las por rota (scoped-*) remove o vazamento, mas custa outro ~9 KB de overhead de chunk.- Intlayer's
staticjá tem 0% de vazamento de página: o compilador só agrupa os dicionários usados pelos componentes na página. HabilitandoimportMode: 'dynamic'(uma linha emintlayer.config.ts) remove também o vazamento de locale. - O tamanho do componente é onde a arquitetura se manifesta: 76-87 KB por componente com
use-intlversus 6-8 KB com Intlayer.useTranslations()vincula cada componente à árvore de mensagens global;useIntlayer()vincula-o ao seu próprio dicionário. - Mudança de locale é 2x-4x mais rápida com Intlayer (3 ms vs 7-21 ms).
Por que a diferença? Catálogos centralizados vs. dicionários compilados
next-intl segue o modelo clássico: um JSON por locale, carregado em getRequestConfig, inserido em um NextIntlClientProvider, lido através de t("namespace.key").
Copiar o código para a área de transferência
O runtime não consegue saber quais keys uma página usará, então o padrão seguro é enviar o catálogo completo. Otimizar significa você dividir o catálogo em namespaces, você decidir quais namespaces cada página precisa, e você manter esse mapeamento sincronizado conforme os componentes se movem. A linha scoped-dynamic do benchmark é a recompensa por esse trabalho, e a maioria das equipes nunca chega lá.
Intlayer inverte a responsabilidade. O conteúdo é declarado ao lado do componente:
Copiar o código para a área de transferência
No momento da compilação, o compilador (@intlayer/swc / @intlayer/babel) vê qual componente importa qual dicionário. Ele agrupa apenas esses dicionários, apenas para o locale ativo, e descarta os que nada importa. O padrão "scoped-dynamic" torna-se a saída da compilação em vez de uma disciplina que o time tem que manter.
Para obter os números da linhadynamic, definadictionary.importMode: 'dynamic'emintlayer.config.ts. Veja a documentação de otimização de bundle.
Experiência do desenvolvedor
Componente cliente
next-intl
Copiar o código para a área de transferência
Copiar o código para a área de transferência
Lembrez-se de incluir o namespacecounternas mensagens passadas paraNextIntlClientProviderem cada página que renderiza este componente.
Intlayer
Copiar o código para a área de transferência
Copiar o código para a área de transferência
Nada para registrar na página: o componente traz seu próprio conteúdo.
Componente de servidor síncrono
Peças de design-system (navbar, footer, cards) são frequentemente componentes server renderizados como children de componentes client, então não podem ser async.
next-intl
Copiar o código para a área de transferência
A página tem que await getTranslations("counter") e await getFormatter(), depois passar os resultados para baixo como props. O componente não é mais auto-contido.
Intlayer
Copiar o código para a área de transferência
Metadados
next-intl
Copiar o código para a área de transferência
Intlayer
Copiar o código para a área de transferência
Mantenha a API do next-intl, obtenha a saída do Intlayer
Você não precisa reescrever componentes para obter os números de benchmark acima. @intlayer/next-intl é um adaptador pronto para usar: mantém useTranslations, getTranslations, useFormatter, t.rich(), plurais ICU e os auxiliares next-intl/navigation, e os fornece a partir dos dicionários compilados do Intlayer pelo compilador Intlayer.
Copiar o código para a área de transferência
Na benchmark, a build de compatibilidade da mesma aplicação passou de 153.6 KB para 147.5 KB por página, de 21.8 KB para 8.1 KB por componente, e de ~90% page leakage para 0%, com o código da aplicação intacto. Seus arquivos messages/{locale}.json existentes podem continuar sendo a fonte de verdade através do plugin JSON sync.
Veja o guia de migração next-intl para o passo a passo.
Quando escolher qual?
- Escolha next-intl se você quer o padrão de ecossistema para Next.js, depende de ICU MessageFormat, sua aplicação é pequena a média, ou você se integra com uma plataforma de tradução (Crowdin, Phrase, Lokalise...) que espera JSON centralizado. Considere o tempo para namespace de catálogos e selecione mensagens por página se o desempenho importa.
- Escolha Intlayer se você quer conteúdo com escopo de componente, TypeScript rigoroso, erros de chaves ausentes em tempo de build, tree-shaking e lazy loading sem esforço, componentes de servidor síncronos, e ferramentas editoriais integradas (Visual Editor, CMS, tradução com IA, servidor MCP). Especialmente relevante para codebases grandes e modulares e design systems.
- Escolha
@intlayer/next-intlse você já está usandonext-intle quer ganhos de bundle sem uma reescrita.
Comparações relacionadas
- i18next vs Intlayer (mesmo benchmark)
- Lingui vs Intlayer (mesmo benchmark)
- vue-i18n vs Intlayer benchmark (mesmo benchmark)
- next-i18next vs next-intl vs Intlayer
- next-intl está desatualizado?
GitHub STARs
As estrelas do GitHub são um forte indicador de popularidade de um projeto, confiança da comunidade e relevância de longo prazo. Embora não sejam uma medida direta de qualidade técnica, elas refletem quantos desenvolvedores acham o projeto útil, acompanham seu progresso e provavelmente vão adotá-lo.
Conclusão
next-intl é uma biblioteca sólida e bem mantida, e o benchmark confirma que está longe de ser a pior opção no Next.js. Mas seu modelo de catálogo centralizado coloca cada otimização nas mãos do desenvolvedor: a configuração ingênua vaza ~90% do conteúdo de páginas estrangeiras, e o runtime sozinho custa +12.6 KB gzip em cada página.
Comentários
Ainda sem comentários. Seja o primeiro a compartilhar seus pensamentos.
