migration2023-12-20·11 min·331/348

Static Generation with next-intl in Next.js App Router

How to configure next-intl for static site generation with all locales pre-rendered at build time

Static Generation with next-intl in Next.js App Router

Introduction

Static generation with internationalization creates a unique challenge: every page must be pre-rendered for every locale at build time. With next-intl and the App Router, this requires careful configuration of generateStaticParams, message loading, and build-time locale handling.

After migrating a multilingual marketing site from SSR to full static generation, I learned exactly how next-intl interacts with Next.js build processes. This post provides a complete working configuration.

Environment

  • OS: Windows 11
  • Node.js: v20.10.0
  • Next.js: 14.0.4
  • next-intl: 3.3.1
  • Locales: en, ko, pt, ja

Problem

Static generation failed with several errors:

Error: Missing locale in generateStaticParams
Error: Cannot find module './messages/en.json'
Error: Export encountered an error reading "/ko/about"
Error: generateStaticParams returned incomplete locale combinations

The build output showed only English pages were generated:

Route (app)                     Size     First Load JS
┌ ○ /                           0 B             0 kB
├ ○ /about                      0 B             0 kB
├ ○ /contact                    0 B             0 kB
└ ○ /blog                       0 B             0 kB
# Missing: /ko/*, /pt/*, /ja/*

Analysis

The issues were:

1. generateStaticParams not returning all locales. Each dynamic route with a [locale] segment must return params for all locales.

2. Message files not accessible during build. The message loading function must work with static file imports, not runtime fetches.

3. Missing output: 'export' or ISR configuration. Without explicit static generation configuration, pages may attempt server-side rendering.

4. Middleware running during build. The locale detection middleware can interfere with static generation if not properly configured.

Solution

Fix 1: Complete next-intl static generation setup

// i18n-config.ts
export const locales = ['en', 'ko', 'pt', 'ja'] as const
export type Locale = (typeof locales)[number]
export const defaultLocale: Locale = 'en'
// i18n.ts
import { getRequestConfig } from 'next-intl/server'
import { locales, type Locale } from './i18n-config'

export default getRequestConfig(async ({ locale }) => {
  // Validate locale
  if (!locales.includes(locale as Locale)) {
    return { messages: {} }
  }

  return {
    messages: (await import(`./messages/${locale}.json`)).default,
  }
})

Fix 2: Generate static params for all locales

// app/[locale]/page.tsx
import { useTranslations } from 'next-intl'
import { setRequestLocale } from 'next-intl/server'
import { locales } from '@/i18n-config'

export function generateStaticParams() {
  return locales.map((locale) => ({ locale }))
}

export default async function HomePage({
  params: { locale },
}: {
  params: { locale: string }
}) {
  setRequestLocale(locale)
  const t = useTranslations('home')

  return (
    

{t('title')}

{t('description')}

) }

Fix 3: Layout with locale support

// app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl'
import { getMessages } from 'next-intl/server'
import { setRequestLocale } from 'next-intl/server'
import { locales } from '@/i18n-config'

export function generateStaticParams() {
  return locales.map((locale) => ({ locale }))
}

export async function generateMetadata({
  params: { locale },
}: {
  params: { locale: string }
}) {
  return {
    title: {
      en: 'English Title',
      ko: '한국어 제목',
      pt: 'Título em Português',
      ja: '日本語タイトル',
    }[locale] || 'Default Title',
  }
}

export default async function LocaleLayout({
  children,
  params: { locale },
}: {
  children: React.ReactNode
  params: { locale: string }
}) {
  setRequestLocale(locale)
  const messages = await getMessages()

  return (
    
      
        
          {children}
        
      
    
  )
}

Fix 4: Middleware configuration for static export

// middleware.ts
import createMiddleware from 'next-intl/middleware'
import { locales, defaultLocale } from './i18n-config'

export default createMiddleware({
  locales,
  defaultLocale,
  localeDetection: false,
})

export const config = {
  matcher: ['/', '/(ko|en|pt|ja)/:path*'],
}

Fix 5: Build configuration

// next.config.js
const createNextIntlPlugin = require('next-intl/plugin')

const withNextIntl = createNextIntlPlugin('./i18n.ts')

/** @type {import('next').NextConfig} */
const nextConfig = {
  // For full static export
  // output: 'export',

  // Or use ISR for partial static generation
  // trailingSlash: true,
}

module.exports = withNextIntl(nextConfig)

Lessons Learned

  1. Every [locale] route needs generateStaticParams. Without it, Next.js cannot pre-render pages for non-default locales.

  2. Use setRequestLocale in every Server Component. This ensures the correct locale is used for translation lookups.

  3. Test the build locally before deploying. Run next build and verify that all locale routes appear in the output.

  4. Keep message files as JSON imports. Dynamic imports work at build time but not with static export.

  5. Consider ISR over full static export for large multilingual sites. ISR pre-renders the default locale and regenerates other locales on demand.

This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.