Support multiple markets
With Shopify Markets, merchants can sell in multiple countries and languages from one store. A headless storefront decides which market each request belongs to, and then asks the Storefront API for data in that market's context.
Hydrogen doesn't decide how your storefront picks a market. You might use subdomains, per-market domains, path prefixes, cookies, or a customer's saved preference. Hydrogen's part is the request context: you resolve the market once on the server, pass it as i18n to createShopifyRequestContext(), and the Storefront API client adds the market's country and language to every query that asks for them.
In this guide, you'll resolve a market from each request, pass it to Hydrogen, and keep Shopify's browser scripts and your links in the same market.
Anchor to RequirementsRequirements
- Set up markets for your store. For more information, refer to Shopify Markets.
- Create a Storefront API client in your server request lifecycle.
- Decide how your storefront maps requests to markets: subdomains, per-market domains, or path prefixes.
Anchor to Step 1: Resolve the marketStep 1: Resolve the market
Write a small, explicit resolver that maps each request to a market. A market has a country and a language, using the uppercase codes that the Storefront API expects, such as US, CA, EN, and FR. Always return a default market so that the request context never has an undefined market.
Market shape
lib/markets.tsKeep any labels or alternate domains that your app needs as extra fields on your own market objects. Only country and language are required. pathPrefix is optional.
Choose one of the following strategies, depending on how your storefront's URLs identify markets.
Anchor to SubdomainsSubdomains
Use subdomains when each market lives under one domain, such as ca.example.com or fr.example.com:
Resolve a market from the subdomain
lib/markets.tsAnchor to Per-market domainsPer-market domains
Use per-market domains when each market has its own domain, such as example.com, example.ca, and example.fr:
Resolve a market from the domain
lib/markets.tsMap hosts from an allowlist like MARKET_BY_HOST. Don't trust the request's host for anything security-sensitive.
Anchor to Path prefixesPath prefixes
Use path prefixes when markets share one domain and the URL path identifies the market, such as /en-ca/products/shirt or /fr-fr/products/shirt. Give each prefixed market a pathPrefix:
Resolve a market from the path prefix
lib/markets.tsYour router needs to match product and collection routes with the prefix in front of them, such as a :market route segment or a localized route group.
Anchor to Step 2: Pass the market to the request contextStep 2: Pass the market to the request context
Resolve the market where your app creates its Shopify request context, and pass it as i18n. Use the same request context for the Storefront API client and for handleShopifyRoutes(). This example shows only the market-specific part of your server request handler. For the rest of the handler, refer to Request handlers.
Pass the market to the request context
lib/shopify.tsCall handleRequest() from your app's request entry point. For where that is in React Router and Next.js, refer to Framework wiring.
The Storefront API client exposes the resolved market as client.i18n. Hydrogen normalizes client.i18n.pathPrefix to either an empty string or a prefix with a leading slash and no trailing slash, such as /en-ca.
In React Router, middleware and loaders receive the full Request, so the resolvers from Step 1 work as written. The next two subsections apply only to Next.js.
Anchor to Resolve the market from forwarded headers in Next.jsResolve the market from forwarded headers in Next. js
In Next.js, Server Components receive only the request headers, not a Request or the current URL. Return requestContext.getForwardedRequestHeaders() from proxy.ts. Those headers include the original URL in the x-storefront-url header, so your server code can resolve the market from them:
Resolve a market from forwarded headers
lib/markets.tsThen create the request context with the headers alone, such as createShopifyRequestContext({request: {headers}, i18n: getMarketFromHeaders(headers)}). Reading headers with headers() is a request-time API, so pages that use this client render dynamically. For path-prefixed markets, use route params instead, as described in the next subsection.
Anchor to Path prefixes in Next.jsPath prefixes in Next. js
In Next.js, model a path-prefixed market as a route segment, such as app/[market]/products/[handle]/page.tsx, and read it from params instead of from headers:
Resolve a market from route params
lib/path-markets.tsBecause the market comes from params, you can prerender pages with generateStaticParams as long as they don't call request-time APIs such as headers() or cookies(). For prerendered or cached pages, use a private_no_buyer_context client with a static request context:
Static client for a market
lib/static-storefront.tsPages that need customer-specific data, such as the cart, need a private client with trusted buyer context. Those pages render dynamically.
Anchor to Step 3: Localize your queriesStep 3: Localize your queries
The client doesn't rewrite your query text. It fills in $country and $language from the request context's i18n when a query declares them. For the Storefront API to use them, the query also needs the @inContext directive:
Market-aware query
lib/queries.tsDon't pass country or language in each client.graphql() call. Resolve the market once at the request boundary and let the client add it.
Render prices from the amount and currencyCode that Shopify returns for the market, and format them with formatMoney() and the market's locale. Don't convert currency in your app.
Anchor to Step 4: Keep scripts and links in the same marketStep 4: Keep scripts and links in the same market
Several parts of your storefront need the same market as your Storefront API queries:
-
Shopify scripts: Pass the resolved market's
countryandlanguageto thei18nprop ofShopifyScriptsso that Shopify's browser globals match your queries. Resolve it on the server and pass it to the component as data. -
Route templates: Keep market prefixes out of your route templates. Hydrogen adds
i18n.pathPrefixto product?variant=redirects, Shopify URL redirects,ShopifyScriptsURLs, and predictive search links. -
Your own links: When your app builds links for a path-prefixed market, add
client.i18n.pathPrefixin front of the path:const href = `${storefrontClient.i18n.pathPrefix}/products/${product.handle}`; -
Predictive search: Pass
pathPrefix: i18n.pathPrefixtogetPredictiveSearchItemUrl(). Refer to Add predictive search.
Shopify Markets localizes Shopify data, such as product titles, prices, and availability. Your app's own interface text, such as button labels, still needs your app's translation system.
If your storefront detects a market from the customer's location, then treat that as a suggestion. Customers travel, use VPNs, and choose markets on purpose. If customers can switch markets in your storefront, then save their choice.
Anchor to Step 5: Test market resolutionStep 5: Test market resolution
Your resolver takes a standard Request, so you can test it without a framework test harness:
Market resolver test
lib/markets.test.tsAlso test at least one Storefront API call with a mock fetch, and check that the GraphQL variables include the expected country and language. To learn how to pass a mock fetch to the client, refer to Storefront API client.