Skip to main content

Setup multilingual and multi-regional storefronts with domains and subdomains

In this guide you will learn how to setup your Hydrogen project for supporting multiregion and multilingual storefronts by using domains and subdomains. For example, say you have a storefront that should work in English (EN) and in non-regional French (FR) for different customers.

Set up the project to handle requests as follows:

LanguageURL path
Englishca.hydrogen.shop
Frenchhydrogen.fr


Anchor to Step 1: Create a utility that reads requested hostStep 1: Create a utility that reads requested host

Create a utility function that reads the requested host and return the right Locale object using the Storefronts API's supported language and country codes.

Tip

You can use the /app/lib/utils.js in the Hydrogen demo store as a point of reference.

The following is an example utility function with the following locales en_CA, fr_CA and en_US.

utils

/app/lib/utils.js

export function getLocaleFromRequest(request) {
// Get the user request URL
const url = new URL(request.url);

// Match the URL host
switch (url.host) {
case 'ca.hydrogen.shop':
return {
language: 'EN',
country: 'CA',
};
break;
case 'hydrogen.fr':
return {
language: 'FR',
country: 'CA',
};
break;
default:
return {
language: 'EN',
country: 'US',
};
}
}
export function getLocaleFromRequest(request: Request): Locale {
// Get the user request URL
const url = new URL(request.url);

// Match the URL host
switch (url.host) {
case 'ca.hydrogen.shop':
return {
language: 'EN',
country: 'CA',
};
break;
case 'hydrogen.fr':
return {
language: 'FR',
country: 'CA',
};
break;
default:
return {
language: 'EN',
country: 'US',
};
}
}

The Locale object that returned should resemble the following example, which is using the Storefont API's supported language and country codes.

TypeScript

import {
CountryCode,
LanguageCode,
} from '@shopify/storefront-kit-react/storefront-api-types';

export type Locale = {
language: LanguageCode;
country: CountryCode;
};

Anchor to Step 2: Add i18n to the storefront clientStep 2: Add i18n to the storefront client

In your server.js, update i18n to the result of the utility function that you used when creating the Hydrogen storefront client.

By doing this, you now have the locale available throughout the app for every storefront query.

server

/server.js

const {storefront} = createStorefrontClient({
// ...
i18n: getLocaleFromRequest(request),
// ...
});
const {storefront} = createStorefrontClient({
// ...
i18n: getLocaleFromRequest(request),
// ...
});

Anchor to Step 3: Add @inContext directive to your GraphQL queriesStep 3: Add @inContext directive to your GraphQL queries

To support international pricing and languages in Storefront API, you need to pass the $country and $language with an @inContext directive within any requests.

Update your GraphQL queries with inContext directives to include $country and $language. Hydrogen automatically injects these parameters.

For example:

GraphQL queries

Before

const FEATURED_QUERY = `#graphql
query homepage {
collections(first: 3, sortKey: UPDATED_AT) {
nodes {
id
title
handle
image {
altText
width
height
url
}
}
}
}
`;

After

const FEATURED_QUERY = `#graphql
query homepage($country: CountryCode, $language: LanguageCode)
@inContext(country: $country, language: $language) {
collections(first: 3, sortKey: UPDATED_AT) {
nodes {
id
title
handle
image {
altText
width
height
url
}
}
}
}
`;

You don't need to manually provide query variables for country and language. You can make these queries with storefront.query in the data loader and you should see the right language and currencies for each request.

export async function loader({
context: {storefront},
}) {
return json({
featureCollections: await storefront.query<{
collections;
}>(FEATURED_COLLECTIONS_QUERY),
});
}
export async function loader({
context: {storefront},
}: LoaderArgs) {
return json({
featureCollections: await storefront.query<{
collections: CollectionConnection;
}>(FEATURED_COLLECTIONS_QUERY),
});
}

Hydrogen injects the locale parameters automatically to storefront.query based on what was defined in i18n when creating the client.

For example, if a request came from hydrogen.fr, then the country CA and language FR are used as defined in the example utilities function above.

The Storefront API returns the correct currency and language if the store was set up in the Shopify admin.

Note

if you want to override the locale determined by your utility option, you can supply the query variables to the storefront.query:

export async function loader({
context: {storefront},
}) {
return json({
featureCollection: await storefront.query(FEATURED_COLLECTIONS_QUERY, {
variables: {
country: 'CA', // Always query back in CA currency
language: 'FR', // Always query back in FR language
}
}),
});
}
export async function loader({
context: {storefront},
}: LoaderArgs) {
return json({
featureCollection: await storefront.query<{
collections: CollectionConnection;
}>(FEATURED_COLLECTIONS_QUERY, {
variables: {
country: 'CA', // Always query back in CA currency
language: 'FR', // Always query back in FR language
}
}),
});
}

Anchor to Step 4: Make sure redirects are properly url encodedStep 4: Make sure redirects are properly url encoded

If you have multilingual handles for your product or collection, for example, products/スノーボード, make sure to encode url when making redirects.

Link

/app/routes/($locale).products.$productHandle.js

export async function loader({params, request, context}) {
const {productHandle} = params; // productHandle = 'スノーボード'

...

if (noSelectedProductVariant) {
// Use URL to prevent accidental double url encoding
const newUrl = new URL(
`/products/${productHandle}?${firstVariantSearchParams.toString()}`,
'http://example.com' // Any domain to satisfy the URL api
);

// Redirect to '/products/%E3%82%B9%E3%83%8E%E3%83%BC%E3%83%9C%E3%83%BC%E3%83%89?Size=154cm&Color=Syntax'
throw redirect(newUrl.pathname + newUrl.search, 302);
}

...
export async function loader({params, request, context}: LoaderArgs) {
const {productHandle} = params; // productHandle = 'スノーボード'

...

if (noSelectedProductVariant) {
// Use URL to prevent accidental double url encoding
const newUrl = new URL(
`/products/${productHandle}?${firstVariantSearchParams.toString()}`,
'http://example.com' // Any domain to satisfy the URL api
);

// Redirect to '/products/%E3%82%B9%E3%83%8E%E3%83%BC%E3%83%9C%E3%83%BC%E3%83%89?Size=154cm&Color=Syntax'
throw redirect(newUrl.pathname + newUrl.search, 302);
}

...


Was this page helpful?