CommerceStorefront Development
Regions
Multi-region and multi-currency handling in your storefront
This guide covers region detection, currency display, and multi-region cart management in your storefront.
Region Detection
Detect the customer's region at the start of their session:
// Fetch all regions
const regions = await commerce.regions.list()
// Auto-detect from browser locale
function detectRegion(regions: Region[]): Region {
const locale = navigator.language // e.g. "en-US", "de-DE"
const countryCode = locale.split('-')[1] || 'US'
const match = regions.find(r =>
r.countries.some(c => c.iso2 === countryCode)
)
return match || regions.find(r => r.id === 'reg_us')!
}Region Provider
'use client'
import { createContext, useContext, useState, useEffect } from 'react'
import { commerce } from '@/lib/commerce'
import type { Region } from '@hanzo/commerce'
const RegionContext = createContext<{
region: Region | null
regions: Region[]
setRegion: (region: Region) => void
}>({} as any)
export function RegionProvider({ children }: { children: React.ReactNode }) {
const [region, setRegionState] = useState<Region | null>(null)
const [regions, setRegions] = useState<Region[]>([])
useEffect(() => {
commerce.regions.list().then(result => {
setRegions(result)
const saved = localStorage.getItem('regionId')
const match = result.find(r => r.id === saved) || detectRegion(result)
setRegionState(match)
})
}, [])
function setRegion(r: Region) {
setRegionState(r)
localStorage.setItem('regionId', r.id)
}
return (
<RegionContext.Provider value={{ region, regions, setRegion }}>
{children}
</RegionContext.Provider>
)
}
export const useRegion = () => useContext(RegionContext)Region Selector
'use client'
import { useRegion } from '@/components/region-provider'
export function RegionSelector() {
const { region, regions, setRegion } = useRegion()
return (
<select
value={region?.id || ''}
onChange={e => {
const r = regions.find(r => r.id === e.target.value)
if (r) setRegion(r)
}}
>
{regions.map(r => (
<option key={r.id} value={r.id}>
{r.name} ({r.currency})
</option>
))}
</select>
)
}Currency Formatting
Format prices according to the active region's currency:
function formatPrice(amount: number, currency: string, locale?: string): string {
return new Intl.NumberFormat(locale || 'en-US', {
style: 'currency',
currency,
minimumFractionDigits: 2,
}).format(amount / 100)
}
// Usage
formatPrice(2999, 'USD') // "$29.99"
formatPrice(2599, 'EUR', 'de-DE') // "25,99 EUR"
formatPrice(2299, 'GBP', 'en-GB') // "GBP22.99"Region-Scoped Carts
Each cart is scoped to a single region. When a customer changes regions, create a new cart:
async function switchRegion(newRegion: Region) {
setRegion(newRegion)
// Create a new cart in the new region
const newCart = await commerce.carts.create({ regionId: newRegion.id })
// Optionally migrate items from the old cart
if (oldCart) {
for (const item of oldCart.items) {
try {
await commerce.carts.addItem(newCart.id, {
variantId: item.variantId,
quantity: item.quantity,
})
} catch {
// Item may not be available in the new region
}
}
}
localStorage.setItem('cartId', newCart.id)
}Tax Display
Regions control whether prices include or exclude tax:
function PriceDisplay({ price, region }: { price: number; region: Region }) {
const formatted = formatPrice(price, region.currency)
return (
<span>
{formatted}
{region.taxInclusive && <span className="text-xs text-gray-500"> incl. tax</span>}
</span>
)
}European regions typically use tax-inclusive pricing. North American regions use tax-exclusive pricing where tax is calculated and displayed at checkout.
How is this guide?
Last updated on