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)
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.
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).
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.
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
next-intl
Copiare il codice nella clipboard
Copiare il codice nella clipboard
Ricorda di includere il namespacecounternei messaggi passati aNextIntlClientProvidersu ogni pagina che renderizza questo componente.
Intlayer
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.
next-intl
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.
Intlayer
Copiare il codice nella clipboard
Metadati
next-intl
Copiare il codice nella clipboard
Intlayer
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?
- Scegli next-intl se desideri lo standard dell'ecosistema per Next.js, dipendi da ICU MessageFormat, la tua app è di piccole o medie dimensioni, oppure ti integri con una piattaforma di traduzione (Crowdin, Phrase, Lokalise...) che prevede JSON centralizzati. Prevedi il tempo per namespace dei cataloghi e scegli messaggi per pagina se le prestazioni sono importanti.
- Scegli Intlayer se desideri contenuto con scope dei componenti, strict TypeScript, errori di chiavi mancanti in fase di build, tree-shaking e lazy loading senza sforzo, server components sincroni, e tooling editoriale integrato (Visual Editor, CMS, traduzione AI, server MCP). Particolarmente rilevante per codebase grandi e modulari e design systems.
- Scegli
@intlayer/next-intlse sei già sunext-intle desideri i vantaggi di bundle senza una riscrittura completa.
Confronti correlati
- i18next vs Intlayer (stesso benchmark)
- Lingui vs Intlayer (stesso benchmark)
- vue-i18n vs Intlayer benchmark (stesso benchmark)
- next-i18next vs next-intl vs Intlayer
- next-intl è obsoleto?
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.
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.
