Next.js Integration
Integrate LocaleLens with Next.js App Router or Pages Router
This repository shows a minimal, server-first i18n setup for Next.js using LocaleLens. Translations are fetched server-side at runtime via a simple API call — no JSON files, no frameworks, no locale routing.
Translations are fetched server-side using a LocaleLens API key. The key is never bundled into client-side code.
Why this approach?
- •Server-first by default
- •Works with caching and ISR
- •No client-side translation state
- •LocaleLens is the source of truth
Using the Pages Router? The guide below covers Pages Router integration with next-i18next.
This guide uses next-i18next, the commonly used i18n library for Pages Router applications.
npm install next-i18next react-i18next i18next1. Create next-i18next.config.js
// next-i18next.config.js
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'es', 'fr'],
},
reloadOnPrerender: process.env.NODE_ENV === 'development',
}2. Update next.config.js
// next.config.js
const { i18n } = require('./next-i18next.config')
module.exports = {
i18n,
// ... other config
}3. Create API Route for Translations
Create pages/api/translations/[locale].ts:
// pages/api/translations/[locale].ts
import type { NextApiRequest, NextApiResponse } from 'next'
const API_URL = 'https://localelens.ai/api/v1'
const PROJECT_ID = process.env.LOCALELENS_PROJECT_ID
const API_KEY = process.env.LOCALELENS_API_KEY
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { locale } = req.query
try {
const response = await fetch(
`${API_URL}/projects/${PROJECT_ID}/translations/${locale}`,
{
headers: {
'Authorization': `Bearer ${API_KEY}`,
},
}
)
if (!response.ok) {
throw new Error('Failed to fetch translations')
}
const translations = await response.json()
res.setHeader('Cache-Control', 'public, s-maxage=3600, stale-while-revalidate=86400')
res.status(200).json(translations)
} catch (error) {
res.status(500).json({ error: 'Failed to load translations' })
}
}⚠️ Security Warning: Never expose LOCALELENS_API_KEY client-side. This API route must remain server-only. The environment variable is only accessible in API routes and will not be bundled with your client code.
4. Create Custom Backend for i18next
Create lib/localelens-backend.ts:
// lib/localelens-backend.ts
import { BackendModule, ReadCallback } from 'i18next'
const LocaleLensBackend: BackendModule = {
type: 'backend',
init() {},
read(language: string, namespace: string, callback: ReadCallback) {
fetch(`/api/translations/${language}`)
.then((res) => res.json())
.then((data) => callback(null, data))
.catch((error) => callback(error, null))
},
}
export default LocaleLensBackend5. Update _app.tsx
// pages/_app.tsx
import type { AppProps } from 'next/app'
import { appWithTranslation } from 'next-i18next'
import nextI18NextConfig from '../next-i18next.config'
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
export default appWithTranslation(MyApp, nextI18NextConfig)6. Environment Variables
Add to .env.local:
LOCALELENS_PROJECT_ID=your_project_id
LOCALELENS_API_KEY=ll_your_api_keyServer-Side Props
// pages/index.tsx
import { GetStaticProps } from 'next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { useTranslation } from 'next-i18next'
import nextI18NextConfig from '../next-i18next.config'
export default function Home() {
const { t } = useTranslation('common')
return (
<div>
<h1>{t('welcome')}</h1>
<button>{t('save')}</button>
</div>
)
}
export const getStaticProps: GetStaticProps = async ({ locale }) => {
return {
props: {
...(await serverSideTranslations(
locale ?? 'en',
['common'],
nextI18NextConfig
)),
},
}
}Language Switching
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next'
export function LanguageSwitcher() {
const router = useRouter()
const { i18n } = useTranslation()
const changeLanguage = (locale: string) => {
router.push(router.pathname, router.asPath, { locale })
}
return (
<select
value={i18n.language}
onChange={(e) => changeLanguage(e.target.value)}
>
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
</select>
)
}- •Keep API keys server-side only — see Production Guide for patterns
- •Use
getStaticPropsorgetServerSidePropsto load translations server-side - •Use flat structure for simpler key access (
common.save) - •Enable caching headers in your API route for better performance
- •Use ISR (Incremental Static Regeneration) with
revalidatefor updated translations