Pose una domanda e ottieni un riassunto del documento facendo riferimento a questa pagina e al provider AI di tua scelta
Il contenuto di questa pagina è stato tradotto con un'IA.
Vedi l'ultima versione del contenuto originale in ingleseSe hai un’idea per migliorare questa documentazione, non esitare a contribuire inviando una pull request su GitHub.
Collegamento GitHub alla documentazioneCopia il Markdown del documento nella porta-documenti
next-intl VS Intlayer: Benchmark di Internazionalizzazione (i18n) per Next.js

next-intl è la libreria i18n più popolare per Next.js. Intlayer è un'alternativa basata su compiler e component-scoped. Entrambi localizzano un'applicazione App Router. La domanda è quanto costi ciascuno una volta che l'app è compilata.
Questo articolo non è un tutorial. È un confronto supportato da numeri da Benchmark Bloom, una suite di benchmark open-source che compila la stessa applicazione con ciascuna libreria e misura ciò che il browser scarica effettivamente ed esegue.
tl;dr: Sulla stessa app Next.js,next-intlaggiunge +12.6 KB gzip di JavaScript su ogni pagina, rispetto ai +0.3 KB di Intlayer. Senza lavoro extra,next-intlspedisce ~90% delle stringhe di pagine straniere con ogni pagina. Raggiungere lo 0% di leakage connext-intlrichiede namespace scoping e per-pagepick(messages, [...]). Intlayer raggiunge lo 0% di default, perché il suo compiler definisce l'ambito dei contenuti per componente. Se vuoi l'API dinext-intlcon l'output di Intlayer, l'adapter@intlayer/next-intlha misurato 147.5 KB per pagina rispetto ai 153.6 KB con l'originale.
In breve
- next-intl - Leggero, ben documentato, supporto per il formato di messaggi ICU, supporto di first-class per App Router con middleware, formattatori e helper di navigazione. I contenuti risiedono in cataloghi JSON centralizzati; le ottimizzazioni delle prestazioni (namespace, selezione dei messaggi per pagina, lazy loading) sono a tua responsabilità.
- Intlayer - Modello di contenuto incentrato sui componenti. I dizionari
.content.tssi trovano accanto al componente che servono, un compiler in fase di build tree-shake e lazy-load per ogni componente e per ogni locale, i tipi TypeScript rigorosi sono generati dal tuo contenuto, e le traduzioni mancanti falliscono in fase di build. Include middleware, helper SEO, un Visual Editor / CMS e traduzione assistita dall'IA.
Apri la tabella in una finestra modale per visualizzare tutti i dati in modo chiaro
I badge si aggiornano automaticamente. Gli snapshot varieranno nel tempo.
Confronto delle funzionalità fianco a fianco
Apri la tabella in una finestra modale per visualizzare tutti i dati in modo chiaro
| Funzionalità | next-intlayer (Intlayer) | next-intl |
|---|---|---|
| Traduzioni vicino ai componenti | ✅ Sì, .content.ts collocato con ogni componente | ❌ No, centralizzato in messages/{locale}.json |
| Integrazione TypeScript | ✅ Tipi rigorosi generati automaticamente dal contenuto | ✅ Buona, chiavi tipizzate tramite augmentation di global.d.ts |
| Rilevamento traduzioni mancanti | ✅ Errore TypeScript + errore/avviso a livello di build | ⚠️ Fallback a runtime + avviso console |
| Rich content (JSX / Markdown / components) | ✅ Supporto diretto | ⚠️ t.rich() / t.markup() con placeholder di tag |
| Supporto ICU | ⚠️ WIP | ✅ Sì |
| Formattazione (date, numeri, valute) | ✅ useNumber, useDate, ... (Intl sotto il cofano) | ✅ useFormatter() (Intl sotto il cofano) |
| Routing localizzato & middleware | ✅ Proxy/middleware integrato, getMultilingualUrls | ✅ Middleware integrato, Link, redirect, usePathname |
| Helper SEO (hreflang, sitemap, robots) | ✅ Helper integrati | ⚠️ Manuale, basato sulla configurazione del routing |
| Componenti server sincroni | ✅ useIntlayer da next-intlayer/server funziona in qualsiasi componente server figlio | ⚠️ getTranslations è asincrono; i figli sincroni necessitano che t sia passato come props |
| Rendering statico | ✅ Non blocca il rendering statico | ⚠️ Richiede setRequestLocale(); i cataloghi con namespace hanno comunque escluso le pagine dal rendering statico nei nostri test |
| Tree-shaking (spedisci solo il contenuto utilizzato) | ✅ Per componente, per locale, automatizzato dal compilatore | ⚠️ Manuale: namespace + pick(messages, [...]) per pagina |
| Lazy loading | ✅ importMode: 'dynamic' (una riga di configurazione) | ⚠️ Import dinamici manuali in getRequestConfig |
| Purge unused content | ✅ Dead dictionaries are dropped at build time | ❌ Not built-in |
| Testing missing translations (CLI / CI) | ✅ npx intlayer content test | ⚠️ Not built-in; docs suggest npx @lingual/i18n-check |
| AI-powered translation | ✅ Built-in, uses your own provider keys | ❌ No |
| Visual Editor / CMS | ✅ Editor Visivo Gratuito + CMS opzionale | ❌ No (piattaforme di localizzazione esterne) |
| MCP server & Agent Skills | ✅ Sì | ❌ No |
| Ecosystem / community | ⚠️ Più piccolo ma in crescita veloce | ✅ Grande, il riferimento di Next.js |
Il benchmark
Cosa è stato misurato
La suite Benchmark Bloom costruisce la stessa applicazione con ogni libreria: 10 pagine (home, about, blog, careers, contact, FAQ, pricing, products, settings, team), 10 locali (en, fr, es, de, it, pt, zh, ja, ko, ru), componenti identici e contenuti identici. Le pagine vengono misurate in en e fr. Ogni libreria è implementata in fino a quattro strategie di caricamento, dal setup naive a quello ottimale:
Apri la tabella in una finestra modale per visualizzare tutti i dati in modo chiaro
| Strategy | Description | Who does this |
|---|---|---|
| static | Ogni locale e ogni pagina raggruppate insieme | Prototipi rapidi, codice generato da AI |
| dynamic | Solo la locale attiva viene caricata, ma tutte le pagine contemporaneamente | La maggior parte dei progetti |
| scoped-static | Namespace per-route, nessun lazy loading | Raro |
| scoped-dynamic | Namespace per-route + lazy loading. Solo la pagina corrente nella locale corrente viene inviata | App con un budget di performance rigoroso |
Intlayer non ha una variante "scoped": il compiler delimita il contenuto per componente automaticamente, quindi le sue righe static e dynamic sono già scoped.
Per ogni build, la suite registra:
- Lib size: dimensione gzip della libreria i18n in un componente vuoto che la importa solo. Il costo fisso del runtime.
- Page JS: JavaScript gzip scaricato per pagina, mediato su tutte le pagine e i locale.
- Locale leak %: quota di stringhe tradotte trovate nel JS scaricato che appartengono a un locale che l'utente non sta visualizzando (fingerprinted su
enefr, quindi il 50% significa "l'altro locale misurato è completamente presente"; con 10 locale raggruppati, lo spreco reale è maggiore). - Page leak %: quota di stringhe tradotte trovate nel JS scaricato che appartengono a una pagina su cui l'utente non si trova.
- Component avg: dimensione media gzip di ogni componente compilato in isolamento. Mostra quanto runtime i18n un singolo componente introduce.
- E2E reactivity: tempo reale tra la selezione di una nuova locale e l'aggiornamento di
html[lang]nel DOM (Playwright, 5 iterazioni). - Hydration: durata della fase di hydration di React.
I numeri sottostanti provengono dall'esecuzione datata 2026-09-12 connext-intl4.14.2,use-intl4.14.2 eintlayer9.5.1. L'applicazione di test è deliberatamente piccola (poche dozzine di stringhe per locale), quindi le percentuali di dispersione descrivono un pattern: crescono con i tuoi contenuti mentre il costo runtime rimane fisso.
Risultati su Next.js (App Router)
Seleziona le metriche e le librerie che ti interessano:
Metrica
Caricamento JSON dinamico
Carica le traduzioni in modalità lazy durante l'esecuzione
JSON con ambito (namespacing)
Spazi dei nomi di traduzione per pagina
Cos'è questa metrica?
La dimensione totale compressa con gzip del bundle della libreria di internazionalizzazione. Include solo il provider e la logica di recupero dei contenuti dopo il tree-shaking e la minificazione.
Perché è importante?
Una dimensione della libreria più piccola riduce il payload JavaScript iniziale, portando a tempi di download ed esecuzione più rapidi sul client.
Visualizza come
Apri la tabella in una finestra modale per visualizzare tutti i dati in modo chiaro
| Library | Strategy | Lib size (gz) | Page JS avg (gz) | Locale leak | Page leak | Component avg (gz) | E2E reactivity | Hydration |
|---|---|---|---|---|---|---|---|---|
| base (no 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 |
Come leggerlo
- Costo runtime. L'applicazione di base pesa 141.0 KB per pagina.
next-intlla porta a 153.6 KB (+12.6 KB gzip su ogni pagina), Intlayer a 141.3 KB (+0.3 KB). Questo divario non dipende da quante stringhe hai: è il runtime della libreria. - Leakage. Nei due setup che la maggior parte dei team effettivamente distribuisce (
staticedynamic),next-intlconsegna ~90% delle stringhe di pagine straniere con ogni pagina: l'interoen.jsonviene inserito nel provider client. Per arrivare a 0% è necessario utilizzare i setupscoped-*: dividere i cataloghi in namespace, quindipick()quelli corretti in ogni pagina. Intlayer è al 0% in entrambe le righe senza nulla di tutto ciò. - Il JS per pagina non si è mosso per
next-intltra le strategie. Il contenuto del test è piccolo, quindi la leakage ~90% è solo di pochi KB qui. Su un'app reale con centinaia di stringhe per pagina, quel rapporto diventa il costo dominante. Nel frattempo il runtime +12.6 KB viene pagato in ogni configurazione. - Dimensione del componente. Un componente che chiama
useTranslations()compila in media a 21.8 KB; lo stesso componente conuseIntlayer()compila a 6.9 KB. Nella configurazionescoped-statici componentinext-intlaumentano a 80.1 KB perché ognuno inserisce il suo catalogo di namespace. - Reattività e idratazione sono sullo stesso livello per entrambe le librerie su Next.js (15-18 ms). Nessuna è un collo di bottiglia qui.
Tabella completa, ogni libreria e ogni strategia, nel report di benchmark Next.js.
Risultati su TanStack Start (use-intl)
use-intl è il core indipendente dal framework di next-intl. Stessa API, stesso formato di messaggio. Confrontarlo con intlayer su TanStack Start rimuove le parti specifiche di Next.js dall'equazione.
Apri la tabella in una finestra modale per visualizzare tutti i dati in modo chiaro
| Libreria | Strategia | Dimensione lib (gz) | Media JS pagina (gz) | Locale leak | Page leak | Media componente (gz) | Reattività E2E |
|---|---|---|---|---|---|---|---|
| base (no 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 |
Come leggerlo
- La configurazione naive di
use-intlinvia 68.8 KB di JS in più per pagina rispetto all'app base, con metà delle stringhe appartenenti alla locale sbagliata e il 90% alla pagina sbagliata. use-intlin modalitàdynamicarriva a 119.4 KB, vicino ai 118.6 KB di Intlayer, ma comunque mantiene 89.8% di page leakage: tutte le stringhe delle pagine per la locale attiva vengono caricate su ogni pagina. Circoscriverle per route (scoped-*) rimuove la perdita ma costa altri ~9 KB di overhead del chunk.- La riga
staticdi Intlayer ha già 0% di page leakage: il compiler raggruppa solo i dizionari utilizzati dai componenti sulla pagina. AbilitandoimportMode: 'dynamic'(una riga inintlayer.config.ts) rimuove anche la locale leakage. - La dimensione del componente è dove l'architettura si mostra: 76-87 KB per componente con
use-intlversus 6-8 KB con Intlayer.useTranslations()lega ogni componente all'albero dei messaggi globale;useIntlayer()lo lega al suo dizionario personale. - Cambio di locale è 2-4 volte più veloce con Intlayer (3 ms vs 7-21 ms).
Tabella completa nel report di benchmark TanStack Start.
Perché il divario? Cataloghi centralizzati vs. dizionari compilati

next-intl segue il modello classico: un JSON per locale, caricato in getRequestConfig, inserito in un NextIntlClientProvider, letto attraverso t("namespace.key").
Copiare il codice nella clipboard
Il runtime non può sapere quali chiavi una pagina utilizzerà, quindi il valore predefinito sicuro è inviare l'intero catalogo. Ottimizzare significa tu dividi il catalogo in namespace, tu decidi quali namespace ogni pagina ha bisogno, e tu mantieni quel mapping sincronizzato mentre i componenti si muovono. La riga scoped-dynamic del benchmark è la ricompensa per quel lavoro, e la maggior parte dei team non ci arriva mai.
Il costo di non arrivarci cresce su due assi contemporaneamente, pagine e impostazioni locali:

Intlayer capovolge la responsabilità. Il contenuto è dichiarato accanto al componente:
Copiare il codice nella clipboard
Al momento della compilazione il compiler (@intlayer/swc / @intlayer/babel) vede quale componente importa quale dizionario. Raggruppa solo i dizionari utilizzati, solo per la locale attiva, e scarta quelli che nessuno importa. Il pattern "scoped-dynamic" diventa l'output della build invece di essere una disciplina che il team deve mantenere.
Per ottenere i numeri della rigadynamic, impostadictionary.importMode: 'dynamic'inintlayer.config.ts. Vedi la documentazione di ottimizzazione del bundle.
Esperienza dello sviluppatore
Componente Client
Copiare il codice nella clipboard
Copiare il codice nella clipboard
Ricorda di includere il namespacecounternei messaggi passati aNextIntlClientProvidersu ogni pagina che renderizza questo componente.
Copiare il codice nella clipboard
Copiare il codice nella clipboard
Niente da registrare sulla pagina: il componente porta con sé il suo contenuto.
Componente server sincrono
I componenti design-system (navbar, footer, cards) sono spesso componenti server renderizzati come figli di componenti client, quindi non possono essere async.
Copiare il codice nella clipboard
La pagina deve await getTranslations("counter") e await getFormatter(), quindi passare i risultati come props. Il componente non è più autonomo.
Copiare il codice nella clipboard
Metadati
Copiare il codice nella clipboard
Copiare il codice nella clipboard
Mantieni l'API di next-intl, ottieni l'output di Intlayer
Non devi riscrivere i componenti per ottenere i numeri di benchmark sopra. @intlayer/next-intl è un adattatore plug-and-play: mantiene useTranslations, getTranslations, useFormatter, t.rich(), plurali ICU e gli helper di next-intl/navigation, e li serve dai dizionari Intlayer compilati dal compilatore Intlayer.
Copiare il codice nella clipboard
Nel benchmark, la build di compatibilità della stessa app è passata da 153.6 KB a 147.5 KB per pagina, da 21.8 KB a 8.1 KB per componente, e da ~90% page leakage a 0%, senza modificare il codice dell'applicazione. I tuoi file messages/{locale}.json esistenti possono rimanere la fonte di verità attraverso il plugin JSON sync.
Consulta la guida di migrazione next-intl per le istruzioni passo dopo passo.
Quando scegliere quale?
Vuoi lo standard dell'ecosistema per Next.js, ti affidi a ICU MessageFormat, la tua app è di dimensioni medio-piccole o ti integri con una piattaforma di traduzione (Crowdin, Phrase, Lokalise...) che prevede JSON centralizzato. Considera il tempo per organizzare i cataloghi in namespace e selezionare i messaggi con pick() per pagina se le prestazioni contano.
Vuoi contenuto con ambito per componente, TypeScript rigoroso, errori di chiavi mancanti in fase di compilazione, tree-shaking e caricamento lazy senza sforzo, componenti server sincroni e strumenti editoriali integrati (Editor Visuale, CMS, traduzione AI, server MCP). Particolarmente rilevante per codebase modulari di grandi dimensioni e design system.
Utilizzi già next-intl e desideri i vantaggi in termini di dimensioni del bundle senza dover riscrivere tutto. L'adattatore di compatibilità mantiene le importazioni e il file messages/{locale}.json come unica fonte di verità. Misurato fianco a fianco in next-intl vs @intlayer/next-intl.
FAQ
Non al momento del rendering. La differenza risiede in ciò che viene inviato al browser: next-intl costa +12.6 KB gzip di runtime su ogni pagina e, nelle configurazioni più comuni, invia ~90% delle stringhe di pagine esterne con ogni pagina. Il cambio di lingua e l'idratazione sono paragonabili su Next.js (15-18 ms); su TanStack Start, use-intl impiega 7-21 ms contro i 3-4 ms di Intlayer.
Sì, con la configurazione scoped-dynamic: dividi messages/{locale}.json in un namespace per route, quindi usa pick(messages, [...]) in ogni pagina e mantieni corretta questa mappatura man mano che i componenti cambiano. Le righe scoped-* del benchmark rappresentano proprio questo lavoro. Intlayer raggiunge lo 0% senza tutto questo perché il compilatore isola il contenuto per componente. Consulta ottimizzazione del bundle.
No. @intlayer/next-intl mantiene useTranslations, getTranslations, useFormatter, t.rich(), i plurali ICU e gli helper di navigazione, e li fornisce da dizionari compilati. Una sola riga di plugin in next.config.ts. Passo dopo passo nella guida alla migrazione di next-intl.
Il supporto nativo ICU è in fase di sviluppo sull'API principale. Gli adattatori di compatibilità (@intlayer/next-intl, @intlayer/use-intl) eseguono già ICU: plurali, select, selectordinal, # e {ts, date, long} passano attraverso il risolutore ICU di Intlayer. Leggi formato di messaggio ICU per i dettagli.
Sì. Il plugin di sincronizzazione JSON li legge, divide le chiavi di livello superiore in dizionari e riscrive le traduzioni negli stessi file quando la CLI o il CMS li aggiorna. Il flusso di lavoro dei tuoi traduttori non cambia.
Confronti correlati
Stesso benchmark, altre librerie:
Approfondimenti su next-intl:
Documenti di riferimento:
Per capire da dove vengono queste librerie, leggi la storia dell'i18n in JavaScript.
Star su GitHub
Le star su GitHub sono un forte indicatore della popolarità di un progetto, della fiducia della comunità e della rilevanza a lungo termine. Anche se non sono una misura diretta della qualità tecnica, riflettono quanti developer trovano il progetto utile, seguono il suo progresso e sono propensi ad adottarlo.
Attività dei commit
Le stelle indicano la popolarità. I commit indicano quanto lavoro viene investito in un progetto. Al momento della scrittura, Intlayer conta circa 7.500 commit, più della maggior parte delle librerie confrontate qui e circa 5 volte più di next-intl o next-i18next.
- amannn/next-intl
- aymericzip/intlayer
Commit sul branch predefinito, fonte: API GitHub.
Intlayer è un monorepo, quindi il totale include ogni pacchetto framework, la CLI e la documentazione. Leggi i commit come un segnale di attività, non di qualità.
Download npm
- next-intl
- next-intlayer
Fonte: API dei download del registro npm.
I download premiano le soluzioni più vecchie, non le migliori. Una libreria pubblicata anni fa viene ancora installata da ogni progetto che l'ha scelta allora, da ogni esecuzione della CI e da ogni pacchetto che ne dipende. Il numero misura l'inerzia più che una scelta attuale.
Gli assistenti IA amplificano l'effetto. next-intl, i18next e vue-i18n sono ovunque nel codice su cui sono stati addestrati, quindi li suggeriscono di default, senza confrontare le alternative. Ogni suggerimento aggiunge download, che alimentano il suggerimento successivo. Confronta sul benchmark, non sul numero di download.
Conclusione
next-intl è una libreria solida e ben mantenuta, e il benchmark conferma che è tutt'altro che la peggiore opzione su Next.js. Tuttavia, il suo modello di catalogo centralizzato mette ogni ottimizzazione nelle mani dello sviluppatore: la configurazione ingenua fa filtrare ~90% dei contenuti delle pagine straniere, e il runtime da solo costa +12,6 KB gzip su ogni pagina.
Intlayer sposta quel lavoro nel compiler. Dizionari per-componente, lazy loading per-locale e purging del contenuto non utilizzato sono output di build, non convenzioni. Il risultato sulla stessa app: +0.3 KB per pagina, 0% leakage, componenti 3x più piccoli, e uno switch locale 2x-4x più veloce su TanStack Start.
Tutti i dati grezzi, le app di test e gli script si trovano nel repository Benchmark Bloom. Eseguilo tu stesso.
Fai riferimento al documento 'Why Intlayer?' per maggiori dettagli.
Commenti
Ancora nessun commento. Sii il primo a condividere i tuoi pensieri.
