• Projects
  • About me
  • Comments
  • Blog

i18n in Next.js App Router: Why I Chose next-international

July 31, 2026 · 4 mins
Logo

I am Thomas, a full-stack developer currently looking for a work-study position.

© 2026 Thomas Galabert Portfolio

Navigation

  • Home
  • Projects
  • About me
  • Blog

Social media

  • Mail
  • Linkedin
  • GitHub

Explore

  • Uses
  • Colophon

Blog title

The need for a bilingual website

When you want to reach a broader audience, open the door to international opportunities, or simply design a product with a global scope, offering a multi-language application quickly becomes a necessity.

Yet, internationalization (i18n) remains the kind of feature we tend to push back. In the React ecosystem, we immediately think of heavy configuration files, hard-to-maintain bloatware, and unnecessary weight added to the client-side JavaScript bundle.

With the arrival of Next.js and the App Router, the way we handle i18n has evolved dramatically. The goal is simple: achieve seamless, high-performance, and 100% type-safe bilingual or multilingual management without cluttering the architecture.

Why next-international?

Rather than opting for legacy and heavier solutions like react-i18next or next-intl, next-international stands out as an excellent alternative for modern projects for several reasons:

  • Native type-safety: keys are directly typed via TypeScript (as const). If a translation is missing or contains a typo in the code, the build fails immediately.
  • Built for the App Router: taking full advantage of Server Components and Client Components, the library avoids unnecessarily wrapping the application in heavy client-side providers.
  • Performance and light weight: most of the translation work happens during server-side rendering, ensuring virtually zero impact on the JS bundle sent to the browser.
  • Clean routing: handling the locale in the URL and language detection integrates naturally into Next.js's request system.

Installing the library

pnpm add next-international

Creating typed dictionaries

First, we define the translation files in TypeScript. The first dictionary serves as the reference for global typing:

// locales/fr.ts
export default {
  'hero.title': 'Développeur fullstack, UI/UX designer',
  'hero.subtitle': 'Création d\'interfaces modernes et performantes.',
  'nav.projects': 'Projets',
} as const
// locales/en.ts
export default {
  'hero.title': 'Fullstack developer, UI/UX designer',
  'hero.subtitle': 'Building modern and performant interfaces.',
  'nav.projects': 'Projects',
} as const

Server and client file configuration

We generate the appropriate utilities for each environment (server and client):

// locales/server.ts
import { createI18nServer } from 'next-international/server'
 
export const { getI18n, getScopedI18n, getStaticParams } = createI18nServer({
  fr: () => import('./fr'),
  en: () => import('./en'),
})
// locales/client.ts
import { createI18nClient } from 'next-international/client'
 
export const { useI18n, useScopedI18n, I18nProviderClient } = createI18nClient({
  fr: () => import('./fr'),
  en: () => import('./en'),
})

Handling routing with proxy.ts

To intercept requests, detect the user's preferred language, and handle redirection in Next.js, we configure proxy.ts at the root:

// proxy.ts
import { createI18nMiddleware } from 'next-international/middleware'
import { NextRequest } from 'next/server'
 
const I18nMiddleware = createI18nMiddleware({
  locales: ['fr', 'en'],
  defaultLocale: 'fr',
})
 
export function proxy(request: NextRequest) {
  return I18nMiddleware(request)
}
 
export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}

Usage in a Server Component

On the server side, the getI18n() function is used asynchronously without needing a Provider:

// app/[locale]/page.tsx
import { getI18n } from '@/locales/server'
 
export default async function HomePage() {
  const t = await getI18n()
 
  return (
    <main>
      <h1>{t('hero.title')}</h1>
      <p>{t('hero.subtitle')}</p>
    </main>
  )
}

When should you (or shouldn't you) choose this library?

Like any architectural decision, next-international answers a specific need and involves trade-offs:

  • Ideal for: showcase websites, medium-sized SaaS, blogs, and modern web applications looking for a quick, clean setup that is completely secure thanks to strong typing.
  • Avoid for: massive enterprise projects requiring dynamic management of dozens of languages via an external CMS (such as Phrase or Lokalise) or highly complex ICU pluralization rules.

An ideal compromise for the modern web

next-international successfully fulfills the promise of making internationalization simple and intuitive. By stripping away unnecessary boilerplate and putting type-safety at the heart of development, the library lets you add multi-language support to a Next.js application in just a few minutes.

It is precisely the kind of modern tool developers appreciate: lightweight, fast, and getting out of the way to serve the end-user experience.