A storefront launch plan usually carries one line for localization, and that line is usually about language. Translated strings appear on a working /de route, the translation review passes, and the market is called open.
Ecommerce localization is six problems. Language is the smallest of them. The other five each depend on configuration in the commerce backend, which is how a translated route still prices a German order in dollars.
This guide covers what each layer requires, which decisions are hard to reverse, and what breaks once locale multiplies by currency.
Key takeaways:
Localization covers interface strings, currency display, tax presentation, price sourcing, catalog availability, and payment methods, and only the first two are settled in the frontend.
Redirecting shoppers by Internet Protocol (IP) address costs index coverage, because Googlebot crawls primarily from United States addresses and sends no
Accept-Languageheader.Subpath URLs consolidate domain authority under one deployment, and the price of that is hreflang correctness, which 67% of domains using hreflang get wrong.
Price books buy predictable margins and cost per-currency maintenance, while conversion at request time removes the maintenance and hands back exchange-rate risk on refunds.
Locale belongs in the URL path, because a locale carried in a
Varyheader or written as a cookie on a product response takes that page out of the cache entirely.
Copy link to headingWhat is ecommerce localization?
Ecommerce localization is the practice of adapting a storefront's content, pricing, catalog, and checkout behavior to what shoppers in one market expect to see and, in several jurisdictions, are entitled to see. It spans the frontend and the commerce backend, and those two halves rarely ship in the same release.
The split is visible in tooling. Vercel and Shopify's rebuilt Hydrogen storefront template ships localization as two separate capabilities, and the template's own skills name the boundary. enable-i18n adds locale-prefixed routing, message catalogs, and a locale switcher. enable-shopify-markets adds region-aware pricing, inventory, and payments. A storefront can run the first without the second and look finished from the outside.
Copy link to headingThe six dimensions of a localized storefront
Language is the only dimension that needs no commerce backend configuration, so it ships first and alone. The other five each require a decision somewhere a frontend team does not usually own.
Here is what each dimension asks for, and where the work lands:
Four of the six rows are configured in the commerce backend, which is where a launch stalls after the strings are done.
Copy link to headingWhy the scope of localization decides whether an order can be placed
A useful test is whether opening a new market requires configuration or engineering. A checkout that hardcodes two-decimal currencies and a single tax mode fails that test the day the second market opens, and no translation review will catch it.
Four failures follow from scoping localization as a translation project:
Currency is displayed but not charged: The storefront formats prices in euros while the cart settles in the store's base currency. The shopper sees one number and their bank statement shows another.
Tax appears only at checkout: In markets that require tax-inclusive display, a price that grows at the final step moves the problem from conversion to compliance.
The catalog ignores market rules: Products that cannot be shipped or sold in a region stay listed, and the products meant to be promoted there never surface.
Payment stops at cards: Markets where bank redirects and mobile wallets carry most volume see checkout abandonment that reads as a pricing problem in the analytics.
Each of these traces back to a component nobody wired up.
Copy link to headingCore components of a localized ecommerce storefront
Six components do the work. The first two decide what search engines can index, and the other four decide what the shopper can buy.
Copy link to headingURL architecture: ccTLD, subdomain, or subpath
URL structure is the hardest localization decision to reverse, because it fixes how Google assigns geographic relevance and how domain authority accumulates across markets.
The axes worth comparing are the ones you feel months after launch:
Subdirectories are a practical default for many catalogs because they keep localized pages on one host and reduce infrastructure overhead. Use hreflang, localized content, currency, addresses, and other signals to identify the intended audience.
Copy link to headingHreflang annotations across every locale variant
Hreflang tells Google that several URLs are the same page for different audiences. Google accepts three equivalent methods, HTML link elements, an HTTP Link response header, and an XML sitemap. Using more than one carries no ranking benefit and multiplies what has to stay in sync.
The requirements are unforgiving in a specific way. Every variant must reference every other variant, including itself; annotations that aren't reciprocal are ignored silently, and URLs must be fully qualified.
Use a supported ISO 639-1 language code, optionally followed by an ISO 15924 script code and an ISO 3166-1 Alpha-2 region code. For example, use en-GB, not en-UK. Add x-default for selectors or unmatched visitors.
Copy link to headingCurrency display and settlement currency
Three currency roles may be involved: the currency shown to the customer, the integration or transaction currency, and the settlement currency. Two or more roles can use the same currency. Refund exchange-rate exposure depends on the provider and conversion product, so verify refund behavior before selecting a pricing model.
The distinction shows up in accounting before it shows up on the page. Under Stripe Adaptive Pricing, the conversion cost sits inside the rate the shopper sees as a 2% to 4% fee, waived if they pay in the integration currency.
Copy link to headingPrice sourcing: price books or live conversion
A price book stores an explicit price per currency. Some backends implement this as standalone price records scoped to a currency, and those platforms don't copy price records between currencies, so every currency needs its own book. Conversion at request time is the other model, applying a rate to a base price as the page renders.
Price books buy control and predictable margins, and cost hand-maintenance of every currency. Conversion removes that maintenance and hands back exchange-rate risk, most visibly on a refund issued weeks later that no longer reconciles to what the customer paid.
Copy link to headingCatalog and market availability
Every commerce backend has a market primitive, and they don't agree on shape. Shopify links a catalog to each Market and scopes Storefront API queries with an @inContext(country:) directive. commercetools assigns Product Selections to Stores. BigCommerce requires explicit channel assignment, where an unassigned product cannot be sold on that storefront at all. VTEX bundles catalog, pricing, promotions, and logistics into a trade policy, which is its term for a sales channel.
A frontend that fetches without the right market context shows items that can't be bought in that region, or drops the ones that were meant to be promoted there.
Copy link to headingTax display and local payment rails
Tax presentation is a display rule before it is a calculation rule. EU consumer law requires the total price including value-added tax (VAT) before the consumer is bound, under the Consumer Rights Directive as amended by the Modernisation Directive. Australia and Singapore apply comparable goods and services tax (GST) inclusive display rules. The US goes the other way, adding tax at checkout at a rate that depends on the buyer's state. These constraints are jurisdictional and fact-specific, and naming them is not tax or legal advice.
Payment rails follow the same market logic, and they move. Bank redirects and wallets carry most consumer volume in several European markets, and the Dutch rail is a live example of what that costs to track. The transition from iDEAL to Wero began with iDEAL | Wero co-branding in 2026. The current plan is for merchants to transition by the end of 2027, followed by a gradual phaseout of the iDEAL brand. Treat branding and migration dates as subject to change and confirm them with the payment service provider.
Copy link to heading5 best practices for localizing an ecommerce storefront
The components describe what has to exist. The five decisions below determine whether a storefront built from them survives production traffic.
Copy link to headingSuggest the locale instead of redirecting on IP address
The default pattern in most storefront codebases reads the country from the IP address and redirects. It costs three things, and none of them show up in testing.
Index coverage goes first. Googlebot crawls primarily from United States addresses and sends no Accept-Language header, so a redirect keyed on a guessed locale can leave every other variant uncrawled. Google advises against redirecting on assumed language or location.
Compliance headroom goes next. In the EU, Regulation 2018/302 prohibits rerouting a customer to a different country version of an interface without explicit prior consent, and requires the originally requested version to stay reachable. The Commission opened an evaluation of that regulation in 2025, so check the obligations before a rollout rather than inheriting a 2018 reading of them.
Accuracy is the last of the three. Country-level IP geolocation accuracy exceeds 99%, but city-level accuracy runs between 20% and 75%, and a shopper on a virtual private network (VPN) lands in the wrong store regardless.
Reading Accept-Language, matching it against supported locales, and offering the match keeps the guess reversible. The confirmation goes in a cookie, and you read it ahead of the header on return visits. A redirect that is still wanted belongs on the homepage alone, serving crawlers what any visitor from that address sees, which keeps it out of cloaking territory.
Copy link to headingStore money in minor units and format at render time
Display code breaks on currencies before it breaks on languages, because the number of decimal places is not constant.
Three groups matter, and they belong in the type system rather than the template:
Storing ¥1,000 as 100000 is an off-by-100 error that reaches production because it looks right in a two-decimal test suite. Payment providers publish the currency minor units their APIs expect, and formatting belongs to Intl.NumberFormat at render time, never to string concatenation. Rounding is a separate surface owned by the commerce backend. Shopify applies its own rounding rules to converted prices, turning $14.27 into $14.00, and doesn't expose those rules to storefront code.
Copy link to headingMake hreflang reciprocal and validate it with a crawler
Hreflang breaks without raising an error anywhere, so nothing surfaces until rankings for a locale quietly flatten. Across 374,756 domains, Ahrefs found in 2023 that 67% had at least one hreflang issue. Reciprocity breaks most, because it fails the moment one variant ships a template the others haven't picked up yet.
Validation now has to run outside Google's own tooling. Search Console's International Targeting report was deprecated in 2022, so hreflang correctness belongs in the deploy pipeline, checked by a crawler or an external validator on every build.
Copy link to headingLet the commerce backend's market primitive decide availability
Teams routinely rebuild market logic in the frontend, filtering products by a locale string after fetching a global catalog. That holds until a market has a product the global catalog doesn't carry, or a product that must not appear in one region for licensing reasons.
Passing market context into the query instead lets the backend return the catalog that market is allowed to see. It costs a fetch-layer refactor and a market identifier threaded through every data call, and it buys one source of truth for availability. The case that repays it is the one hardest to test for, a product that should be invisible in one region and prominent in another.
Copy link to headingKeep locale in the URL path and out of the Vary header
Cache keys are where locale strategy shows up as an infrastructure bill. Vercel’s CDN builds a cache key from the request method, URL, host domain, deployment URL, and scheme. Any header named in Vary multiplies that key by every distinct value seen for it.
Vary: X-Vercel-IP-Country produces roughly 200 entries per path. Accept-Language is worse, because browsers send distinct strings for the same language. Vary: Cookie makes a path effectively uncacheable. A response carrying set-cookie is not cached at all, whatever Cache-Control says, so writing a locale cookie on the first product-page response turns caching off for that page. Setting it on the redirect from the language switcher instead keeps the product response cacheable, and that one change fixes the most common source of storefront cache misses.
Encoding locale in the path avoids all of it, because the locale enters the cache key through the URL and needs no Vary header.
Copy link to headingHow Vercel supports ecommerce localization for retail engineering teams
Most of what a localized storefront needs at the routing layer is either a request header or a rewrite. These five are what retail teams reach for first.
Copy link to headingRead the shopper's country without a geolocation lookup service
The usual first move on a localization project is to budget for an IP database subscription and a lookup service in front of the storefront. That adds a dependency to the hot path of every request.
Every request reaching Routing Middleware and Vercel Functions already carries eight geolocation headers, including x-vercel-ip-country, x-vercel-ip-country-region, x-vercel-ip-city, x-vercel-ip-timezone, and x-vercel-ip-postal-code. The geolocation() helper in @vercel/functions returns most of those as one object. The caveat to design around is a proxy in front of the deployment, which makes the values describe the proxy instead of the shopper until an Enterprise team adds Trusted Proxy, which is an add-on rather than an included feature.
Copy link to headingRewrite to the locale path before the request reaches the cache
A locale redirect costs a browser round trip on the first request of every session, which is the request that sets the Largest Contentful Paint number for a new visitor.
Routing Middleware runs globally before the cache, so it can rewrite a request to its locale-prefixed path with the URL staying stable and no round trip added. In Next.js 16 the file is proxy.ts exporting a proxy function on the Node.js runtime, and npx @next/codemod@latest middleware-to-proxy . handles the rename from earlier versions.
A locale match that suggests rather than forces looks like this:
import { match } from '@formatjs/intl-localematcher'import Negotiator from 'negotiator'import { NextRequest, NextResponse } from 'next/server'
const locales = ['en-us', 'de-de', 'fr-fr', 'ja-jp']const defaultLocale = 'en-us'
export function proxy(request: NextRequest) { const { pathname } = request.nextUrl
if ( locales.some( (locale) => pathname === `/${locale}` || pathname.startsWith(`/${locale}/`) ) ) { return }
const stored = request.cookies.get('storefront_locale')?.value const headers = { 'accept-language': request.headers.get('accept-language') ?? '', }
const preferred = new Negotiator({ headers }).languages() const locale = stored ?? match(preferred, locales, defaultLocale)
const url = request.nextUrl.clone() url.pathname = `/${locale}${pathname}`
return NextResponse.rewrite(url)}
export const config = { matcher: ['/((?!_next|api|.*\\..*).*)'],}The stored cookie takes priority over the header, so a shopper who switches markets once keeps that choice without another negotiation.
Copy link to headingChange market rules without shipping a deploy
Which markets are live, which currencies each accepts, and which catalog a region maps to are settings a merchandising team owns. Holding them in the bundle turns every one of those changes into a deploy.
Global Config stores configuration outside the deployment and serves the vast majority of optimized reads within 15ms at P99. Because updates propagate globally rather than providing transactional consistency, keep inventory and other immediately consistent data in a transactional system. Cache one shell per locale and stream the price
Product pages are where locale and currency collide, because the description is identical for everyone in a locale while the price, tax, and availability are not. Caching the whole page per shopper is wasteful, and caching nothing gives up the CDN.
Cache Components splits the response along that line. The layout, navigation, and description render into a shell that caches once per locale path. The per-shopper values stream inside a Suspense boundary:
import { Suspense } from 'react'import { ProductDescription } from '@/components/product-description'import { LocalizedPrice, PriceSkeleton } from '@/components/localized-price'
export default async function ProductPage({ params,}: { params: Promise<{ locale: string; handle: string }>}) { const { locale, handle } = await params const product = await getProduct(handle, locale)
return ( <article> <h1>{product.title}</h1> <ProductDescription content={product.description} /> <Suspense fallback={<PriceSkeleton />}> <LocalizedPrice handle={handle} locale={locale} /> </Suspense> </article> )}Cache Components is enabled with cacheComponents: true in Next.js 16, which replaces the experimental.ppr flag that carried Partial Prerendering through the Next.js 15 canaries. Whether revalidating one locale path also revalidates its siblings isn't documented, so test revalidation scope per path before you open a market.
Copy link to headingStart from a template that separates internationalization from Markets
The costly version of a localization project is discovering in month three that routing and commerce were built as one layer, and that adding a market means touching both.
Starting from the rebuilt Hydrogen template keeps the two apart from the first commit, so a team can ship translated routes early without pretending the commerce side is finished. The template is framework-agnostic, so the same split holds whether the storefront runs on Next.js, Nuxt, or Svelte.
Commerce-UI built dedicated regional pages for Lift Foils, each localized and personalized, that stayed fast during traffic spikes of 200%. Helly Hansen, which operates across 38 global markets, completed a global rollout in under six months and moved its Core Web Vitals from all red to all green within five months.
Copy link to headingShip a localized storefront that still hits the cache
Localization goes well when you scope it as a routing and data problem from the start, and hand translation the share of it that translation owns. Currency, tax, catalog, and payment decide whether the order goes through, and the URL and caching choices made in week one set how expensive those are to add in month six.
Here is what the platform handles so those decisions stay reversible:
Routing Middleware: Adding a market becomes a routing rule instead of a redirect strategy, and the cache key stays clean.
Geolocation headers: Country detection needs no vendor contract, no lookup hop, and nothing to renew.
Global Config: Merchandising changes which markets are live without waiting on a build.
Cache Components: One market's pricing logic stops dictating another market's cache hit rate.
Speed Insights: A performance regression in a single locale surfaces per route, before it spreads across the catalog.
Start a storefront on vercel.com/new, or browse the commerce starters at vercel.com/templates to see the locale routing and Markets split already wired up.
Copy link to headingFrequently asked questions about ecommerce localization
Copy link to headingWhat is the difference between localization and internationalization in ecommerce?
Internationalization is the one-time structural work, covering dynamic route segments, message catalogs, and money stored in minor units. Localization is the per-market work that repeats for every market after it. If launching a third market costs about what the first one did, the internationalization layer was skipped.
Copy link to headingCan I auto-redirect shoppers to their local storefront?
Yes, once the shopper has agreed to it, and only if the storefront they first asked for stays one click away. A redirect compelled by law is the exception that needs no agreement, and it still owes the shopper a clear and specific reason on the page.
Copy link to headingDoes Vercel geolocation need a third-party IP database?
No. Location fields arrive as request headers with nothing to install or subscribe to. A commercial IP database earns its place only for signals those headers do not carry, such as flagging a visitor arriving over a VPN or a hosting provider.
Copy link to headingHow do I generate hreflang tags in the Next.js App Router?
alternates.languages in the Metadata API emits them, exported from a layout or page with a self-referencing entry plus one per alternate. The Pages Router doesn't generate hreflang from its i18n config, so those tags go in next/head by hand.