← Blog
Web DesignMay 28, 2026·8 min read

Why I build native multilingual Next.js routes (IT/EN/ES) by default on every commission.

Mapping international SEO routes in Next.js/Astro prevents rendering lag and eliminates the flaws found when using slow automatic translators. This is the exact architecture running this website, right now, in 9 languages.

마음에 드시나요? 마지막에 ❤️가 기다리고 있어요.

Why multilingual matters more than it looks

A local business thinks in one language. A business that wants international clients — tourism, hospitality, professional services, e-commerce — needs its site to exist, natively, in the languages its actual customers search in. Not as an afterthought bolted on later. Every client I build for who wants reach beyond their home country gets multilingual routing from day one, because retrofitting it onto a single-language site later means redoing the metadata, the URL structure, and often the content strategy from scratch.

Why the two obvious shortcuts both fail

There are two shortcuts everyone reaches for first, and both fail for real SEO. The first is a floating translate widget (Google Translate embed or similar): it runs client-side, after the page has already loaded in the original language, so search engines index the original text — your Spanish visitors searching in Spanish never find a page that reads as Spanish to Google. The second is a single URL with a query parameter, like ?lang=es. Search engines don't reliably treat query-param variants as separate, independently rankable pages — they often get treated as duplicate content of the same URL, which means your Spanish content competes with your English content instead of ranking independently for Spanish searches.

The fix: real routes, one per language

Every language gets its own crawlable path — /it/, /en/, /es/ — each rendered server-side with fully translated content, its own <html lang> attribute, and its own metadata. In Next.js this is what next-intl with localePrefix set to "always" gives you out of the box: instead of one page that swaps text with JavaScript, you get genuinely separate pages that search engines can crawl, index, and rank independently — each one a real, complete page in that language, not a translated skin over the same content.

How this is actually wired on this site

1Define the supported locales and set localePrefix: "always" in the next-intl routing config, so every URL — including the default language — carries an explicit /it/, /en/, /es/ prefix with no ambiguous root route
2Store translated strings in one JSON file per language (messages/en.json, messages/it.json, etc.) so every UI string and every piece of page copy has a home per locale, not scattered across components
3In generateMetadata(), set alternates.canonical to the current locale's URL and alternates.languages to a full map of every locale's equivalent URL — this is what actually emits the hreflang tags search engines use to connect the language variants of the same page
4Set openGraph.locale and a proper og:image per page so shared links preview correctly regardless of which language variant gets shared
5Never machine-translate at request time. Translate once, store it, serve it statically per locale — this keeps Time to First Byte fast and means there's no translation API in the critical rendering path

What this actually earns you

Each language variant ranks on its own terms — an Italian search brings up the Italian page, a Spanish search brings up the Spanish page, and neither competes with or dilutes the other in Google's eyes. Just as important for conversion: visitors land on a page that reads as genuinely written in their language, not one wearing a visible translation widget in the corner. For a restaurant, hotel, or professional service trying to look credible to an international audience, that difference is often the gap between a bounce and a client.

Want this architecture running on your site?

Let's talk →

About

Patrick Chen — indie developer and web designer behind Sublimearts.io. This exact routing setup — next-intl, localePrefix always, per-language metadata — is what's rendering the page you're reading right now, in whichever of the 9 languages you're viewing it in.

Did you find this helpful?