METADATA:{"excerpt":"A bilingual Next.js project on Vercel taught me the hard way about i18n configuration, caching gotchas, and UAE client expectations.","tags":["Next.js","Vercel","i18n","TypeScript","Firebase"]}
Let me take you back to 2 a.m. last March. I was deploying a bilingual Next.js app for a UAE real estate client. The staging environment worked perfectly. But on Vercel's production deployment? Arabic paths were returning 404s. English worked. The app is supposed to handle both.
I stared at the logs for an hour. Then remembered a line in the client's wireframe: "Header navigation must show current language first." That tiny detail forced deeper i18n integration than I'd planned.
The Setup: When a Client Actually Wants Both Languages
We were building an Expo web app (part of Greeny Corner's desktop version) where users in Abu Dhabi could view plant care guides in Arabic or English. No fallbacks – if your browser didn't match, you got a prompt.
Used next-i18next with locales ['en', 'ar-AE']. Backend translations were served via Firebase Realtime Database, while component translations lived in static JSON files. SSR meant rendering proper lang attributes and dir="rtl" for Arabic.
i18n Config That Actually Works
Here's what got me burned: Next.js's built-in i18n doesn't handle dynamic routes well with Vercel's edge caching.
// next.config.js
i18n: {
locales: ['en', 'ar-AE'],
defaultLocale: 'en',
domains: [
{ domain: 'greenycorner.ae', defaultLocale: 'en' },
{ domain: 'ar.greenycorner.ae', defaultLocale: 'ar-AE' }
]
} Had to create custom server headers in vercel.json:
"headers": [
{
"source": "/:path*",
"headers": [
{ "key": "Cache-Control", "value": "public, max-age=0, s-maxage=86400, stale-while-revalidate=3600" },
{ "key": "Vary", "value": "Accept-Language"}
]
}
] This let Vercel's CDN cache both language versions separately.
Deployment Drama: When Your Router Becomes a Detective
First deploy: /ar/care-guide/1 worked locally but failed on Vercel. Turned out my dynamic revalidate() function wasn't serializing locale paths correctly.
Had to modify this on the API side:
// pages/api/care-guide/[id].ts
const locale = req.headers['accept-language']?.includes('ar-AE') ? 'ar-AE' : 'en'
const content = await getTranslation(locale, `care_guides_${id}`)
res.json(content) But client-side navigation via next/router needed the same treatment. Wasted half a day syncing server/client locale detection logic.
Build Time Monsters Aren't Mythical
We added two locales – build time quadrupled to 14 minutes. Vercel's 200 OK responses felt sarcastic after I hit the timeout limit thrice. Found a hack:
For non-critical pages, replaced getStaticProps with client-side fetches using Suspense. Added this script to reduce page regeneration:
// scripts/prune-build.js
const fs = require('fs')
if (process.env.NODE_ENV === 'production') {
fs.readdirSync('public/locales/ar-AE')
.filter(file => new Date(file.modified) < someThreshold)
.forEach(fs.unlinkSync)
} UAE Clients: No "WIP" for Core Features
Here's the thing about UAE businesses: they won't accept "we'll fix language bugs in v2". When I suggested delaying the Arabic launch for Tawasul Limo's booking platform, the client's PM showed me last month's bounce rate stats from Google Analytics – 59% from Arabic speakers hitting English pages.
The Cookie Problem I Almost Missed
We stored preferred language in a cookie. Great idea… until Vercel cached the first response from a bot with no cookie set. Suddenly the default locale was broken.
Final solution:
- Detect locale from
x-vercel-ip-countryheader (Vercel's geolocation) - Set
Set-Cookieheader only after first interaction - Use a
?lang=enquery param as fallback - Add
tags everywhere
Postmortem
Would I change anything?
Yes. For my next deployment to handle Reach Home Properties' mortgage calculator, I'll:
- Test domain routing in a Vercel preview environment before final build
- Use Cloudinary for translated images (spent 3 hours debugging RTL layouts for SVGs)
- Prioritize Firebase Cloud Functions for translation APIs with region: 'me-west1'
If you're deploying a bilingual Next.js app this year: check your cache headers twice, expect 2x build times, and never assume locale detection works across serverless splits.
Got questions? Hit me up on sarahprofile.com/contact. I'm drinking tea while watching cricket scores, and I swear deploying Next apps gets easier with every project… but only after the third 2 a.m. caffeine crash.