i18n Build Strategies — Multi-Build and Single-Build Approaches
In this tutorial, you will learn about i18n build strategies. We cover key concepts, practical examples, and best practices to help you master this topic.
i18n build strategies include multi-build (separate deployment per locale) and single-build (runtime locale switching) approaches for multilingual apps.
What You'll Learn
By the end of this tutorial, you'll understand the trade-offs between multi-build and single-build strategies, how to implement each approach, when to use subdomain vs path-based builds, and how to optimize build performance for multiple locales.
Why It Matters
The build Strategy determines your deployment complexity, build time, CDN Caching behavior, and runtime performance. A single-build approach is simpler but serves unused translations to every user. A multi-build approach optimizes per-locale delivery but increases build and deployment complexity. The right choice depends on your number of locales, page count, and performance requirements.
Real-World Use
A documentation site with 8 languages uses multi-build strategy. Each locale is a separate build with only that locale's translations embedded. CDN caching is per-locale, and users never download translations for other languages. Building 8 locales takes 8 minutes (1 minute each) and can be parallelized. The total output is 8 independent sites, each optimized for one language.
Strategy Comparison
graph LR
A[i18n Build
Strategies] --> B[Single Build
Runtime locale switching]
A --> C[Multi Build
Separate build per locale]
A --> D[Hybrid
Single build, lazy locales]
B --> E[One deployment]
B --> F[All translations in bundle]
B --> G[Simple infrastructure]
C --> H[One deployment per locale]
C --> I[Only current locale's data]
C --> J[Optimal per-locale perf]
D --> K[One deployment]
D --> L[Lazy load translations]
D --> M[Good perf + simple infra]
style A fill:#4a90d9,color:#fff
style C fill:#27ae60,color:#fff
style D fill:#f39c12,color:#fff
Multi-Build Strategy
// build/i18n-multi-build.js — Multi-build strategy implementation
const fs = require('fs-extra');
const path = require('path');
const { execSync } = require('child_process');
const locales = ['en', 'es', 'fr', 'de', 'ar', 'ja'];
const baseUrl = 'https://example.com';
async function buildLocale(locale) {
console.log(`Building locale: ${locale}`);
// Set environment variables for this locale
process.env.LOCALE = locale;
process.env.BASE_URL = `${baseUrl}/${locale}`;
// Run the build command (e.g., Next.js, Vite, Webpack)
execSync('npm run build', {
env: {
...process.env,
NEXT_PUBLIC_LOCALE: locale,
NEXT_PUBLIC_BASE_URL: `${baseUrl}/${locale}`
},
stdio: 'inherit'
});
// Move output to locale-specific directory
const outputDir = path.join('dist', locale);
await fs.move('out', outputDir, { overwrite: true });
// Generate locale-specific sitemap
await generateSitemap(locale, outputDir);
// Generate locale-specific robots.txt
await fs.writeFile(
path.join(outputDir, 'robots.txt'),
generateRobots(locale)
);
console.log(`Completed locale: ${locale}`);
}
async function generateSitemap(locale, outputDir) {
const urls = [
`${baseUrl}/${locale}/`,
`${baseUrl}/${locale}/about`,
`${baseUrl}/${locale}/products`,
`${baseUrl}/${locale}/contact`
];
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.map(url => ` <url><loc>${url}</loc></url>`).join('\n')}
</urlset>`;
await fs.writeFile(path.join(outputDir, 'sitemap.xml'), sitemap);
}
function generateRobots(locale) {
return `User-agent: *
Allow: /
Sitemap: ${baseUrl}/${locale}/sitemap.xml`;
}
// Build all locales (in parallel)
async function buildAll() {
await Promise.all(locales.map(buildLocale));
console.log('All locales built successfully');
}
// Build all locales with progress
async function buildAllSequential() {
for (const locale of locales) {
const start = Date.now();
await buildLocale(locale);
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
console.log(`Built ${locale} in ${elapsed}s`);
}
}
// Generate root index page that redirects based on Accept-Language
async function generateRootIndex() {
const html = `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Example</title>
<script>
const locales = ${JSON.stringify(locales)};
const lang = navigator.language.split('-')[0];
const matchedLang = locales.includes(lang) ? lang : 'en';
window.location.href = '/' + matchedLang + '/';
</script>
</head>
<body>
<a href="/en/">English</a>
<a href="/es/">Espanol</a>
<a href="/fr/">Francais</a>
</body>
</html>`;
await fs.writeFile(path.join('dist', 'index.html'), html);
}
// Run: node build/i18n-multi-build.js
Single-Build Strategy
// build/i18n-single-build.js — Single-build with runtime locale switching
const fs = require('fs-extra');
const path = require('path');
// Webpack/Vite config for single build
// All translations are bundled, locale is determined at runtime
/*
Webpack config:
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
plugins: [
// i18n plugin that includes all translation files
new I18nPlugin({
locales: ['en', 'es', 'fr', 'de', 'ar', 'ja'],
// Include ALL translations in bundle
bundleAllLocales: true
})
]
};
*/
// For Vite:
// vite.config.js
// import { defineConfig } from 'vite';
// export default defineConfig({
// plugins: [
// {
// name: 'i18n-include-all',
// transform(code, id) {
// // Transform to include all locale files
// // This is simplified — real implementation uses i18n plugins
// return code;
// }
// }
// ]
// });
// Single build output structure:
// dist/
// index.html
// bundle.js (includes all translations)
// styles.css
// locales/
// en.json
// es.json
// fr.json (can be lazy loaded)
// ...
async function singleBuild() {
console.log('Building single output for all locales');
// Build once — all locales included
execSync('npm run build', { stdio: 'inherit' });
// Generate root-level sitemap with hreflang entries
await generateSitemap();
console.log('Single build complete');
}
async function generateSitemap() {
const locales = ['en', 'es', 'fr', 'de', 'ar', 'ja'];
const baseUrl = 'https://example.com';
// Each URL has hreflang alternates for all locales
const pages = ['/', '/about', '/products', '/contact'];
const entries = pages.map(page => {
const alternates = locales.map(locale =>
` <xhtml:link rel="alternate" hreflang="${locale}" href="${baseUrl}/${locale}${page}" />`
).join('\n');
return ` <url>
<loc>${baseUrl}/en${page}</loc>
${alternates}
<xhtml:link rel="alternate" hreflang="x-default" href="${baseUrl}/en${page}" />
</url>`;
}).join('\n');
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
${entries}
</urlset>`;
await fs.writeFile(path.join('dist', 'sitemap.xml'), sitemap);
}
Hybrid Strategy (Lazy Locales)
// build/i18n-hybrid.js — Hybrid strategy with lazy loaded locales
// Build configuration:
// 1. Common bundle (core app code) — built once
// 2. Per-locale translation chunks — built per locale
// 3. Locale chunks are loaded at runtime based on detection
// Webpack config example:
// {
// entry: './src/index.js',
// output: {
// filename: 'bundle.js',
// },
// plugins: [
// new webpack.ProvidePlugin({
// // Core i18n library in main bundle
// })
// ]
// }
// Translation chunk loading:
// Each locale is a separate async chunk loaded on demand
// /locales/en.chunk.js
// /locales/es.chunk.js
// /locales/fr.chunk.js
async function buildHybrid() {
console.log('Building hybrid: common + locale chunks');
// 1. Build common bundle (core app)
execSync('npm run build:core', { stdio: 'inherit' });
// 2. Build locale chunks in parallel
const locales = ['en', 'es', 'fr', 'de', 'ar', 'ja'];
await Promise.all(locales.map(async (locale) => {
execSync(`npm run build:locale -- --locale=${locale}`, {
stdio: 'inherit'
});
}));
console.log('Hybrid build complete');
}
Deployment Strategies
// deploy/i18n-deploy.js — Deployment strategies for multi-locale builds
// Strategy 1: Separate deployments (multi-build)
// Each locale is deployed independently
// en.example.com → build/en/
// es.example.com → build/es/
// Or:
// example.com/en/ → build/en/ (path-based on same CDN)
// Strategy 2: Single deployment (single-build or hybrid)
// All locales served from one deployment
// Runtime code handles locale routing
// Strategy 3: CDN with lambda@edge (for SSR locales)
// CloudFront + Lambda@Edge to detect locale and serve correct build
async function deployMultiBuild(buildDir, locales) {
for (const locale of locales) {
console.log(`Deploying locale: ${locale}`);
// Deploy to locale-specific path
// execSync(`aws s3 sync ${buildDir}/${locale} s3://bucket/${locale}/ --delete`, {
// stdio: 'inherit'
// });
// Or with Netlify:
// execSync(`npx netlify-cli deploy --dir=${buildDir}/${locale} --alias=${locale}`);
}
}
async function deploySingleBuild(buildDir) {
console.log('Deploying single build');
// Single deployment — locale resolution happens at runtime
// execSync(`aws s3 sync ${buildDir} s3://bucket/ --delete`, {
// stdio: 'inherit'
// });
}
Common Mistakes
- Using single-build for 20+ locales. Bundling all translations for 20+ languages creates massive JS bundles. Use multi-build or hybrid strategies to keep per-locale payload small.
- Not parallelizing multi-builds. Building 10 locales sequentially takes 10x the build time. Use Promise.all or CI matrix builds to build locales in parallel.
- Forgetting locale-specific sitemaps and robots.txt. Each locale build needs its own sitemap.xml and robots.txt. Without them, search engines can't discover all locale versions.
- Inconsistent base URLs between build and deployment. The base URL used during build must match the deployment URL. A locale built for /en/ but deployed to /fr/ will have broken hreflang, canonical, and asset paths.
- Not testing the root URL redirect. The root domain (example.com) must redirect users to the correct locale. Test with different browser languages to ensure the redirect works correctly.
Practice Questions
- What are the trade-offs between single-build and multi-build strategies?
- How does the hybrid strategy balance build complexity and performance?
- Why would you choose multi-build for sites with 20+ locales?
- How do CDN caching strategies differ between single-build and multi-build?
- How do you handle locale-specific SEO metadata in a multi-build setup?
Challenge: Given a site with 12 locales and 500 pages each, design and implement a build strategy. Create benchmarks for single-build, multi-build (parallel), and hybrid approaches. Measure: total build time, per-locale JS bundle size, CDN storage requirements, and deployment time for each strategy.
FAQ
Mini Project
Build both a single-build and multi-build version of a multilingual static site with 4 locales and 10 pages each. Compare: build time, output size per locale, total storage, deployment complexity, and runtime performance (bundle size, page load time). Document which strategy is better for different scale scenarios.
What's Next
You've mastered i18n build strategies. Next, learn about i18n Testing for testing internationalized applications.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro