From 26ba5c69c9a4d8b3afa39e07be207dc7077f0d8a Mon Sep 17 00:00:00 2001 From: Boris Besemer Date: Wed, 15 Apr 2026 22:34:15 +0200 Subject: [PATCH 1/2] fix currency fallbacks to use Shopify default --- .../docs/reference/troubleshooting.mdx | 2 +- .../app/collections/md/[handle]/route.ts | 3 + apps/template/app/search/md/route.ts | 3 + apps/template/app/search/page.tsx | 3 + apps/template/components/cart/context.tsx | 14 ++++- .../components/collections/filter-sidebar.tsx | 61 +++++++++++++++---- .../components/collections/filters.tsx | 3 +- apps/template/components/search/results.tsx | 3 + apps/template/lib/collections/server.ts | 3 + apps/template/lib/i18n/index.ts | 3 +- apps/template/lib/markdown/catalog.ts | 8 +-- apps/template/lib/markdown/collection.ts | 4 +- apps/template/lib/markdown/search.ts | 4 +- apps/template/lib/shopify/operations/shop.ts | 28 +++++++++ apps/template/lib/utils/index.ts | 12 ++++ .../2026-04-15-shopify-default-currency.md | 43 +++++++++++++ 16 files changed, 172 insertions(+), 25 deletions(-) create mode 100644 apps/template/lib/shopify/operations/shop.ts create mode 100644 packages/plugin/template-rollout-log/2026-04-15-shopify-default-currency.md diff --git a/apps/docs/content/docs/reference/troubleshooting.mdx b/apps/docs/content/docs/reference/troubleshooting.mdx index 85963e5a..50586277 100644 --- a/apps/docs/content/docs/reference/troubleshooting.mdx +++ b/apps/docs/content/docs/reference/troubleshooting.mdx @@ -65,7 +65,7 @@ To get near-instant updates, set up Shopify webhooks pointed at `/api/webhooks/s ## Locale or currency not changing -Multi-locale support requires Shopify Markets to be enabled. The template ships as single-locale by default. Run [`/vercel-shop:enable-shopify-markets`](/docs/skills/enable-shopify-markets) to add multi-locale and multi-currency routing. +Single-locale storefronts inherit the shop's default Shopify currency for pricing and filter chrome. Multi-locale support still requires Shopify Markets to be enabled. Run [`/vercel-shop:enable-shopify-markets`](/docs/skills/enable-shopify-markets) to add multi-locale and multi-currency routing. ## Agent can't find context files diff --git a/apps/template/app/collections/md/[handle]/route.ts b/apps/template/app/collections/md/[handle]/route.ts index 3836510f..c7deb619 100644 --- a/apps/template/app/collections/md/[handle]/route.ts +++ b/apps/template/app/collections/md/[handle]/route.ts @@ -5,6 +5,7 @@ import { buildProductFiltersFromParams, getCollectionProducts, } from "@/lib/shopify/operations/products"; +import { getShopDefaultCurrencyCode } from "@/lib/shopify/operations/shop"; import { transformShopifyFilters } from "@/lib/shopify/transforms/filters"; import { RESULTS_PER_PAGE, parseFiltersFromSearchParams, searchParamsToRecord } from "@/lib/utils"; @@ -52,11 +53,13 @@ export async function GET(request: Request, { params }: { params: Promise<{ hand const transformedFilters = transformShopifyFilters(result.filters, { activeFilters }); const hasPriceRange = result.filters.some((filter) => filter.type === "PRICE_RANGE"); + const currencyCode = result.products[0]?.price.currencyCode ?? (await getShopDefaultCurrencyCode()); const markdown = collectionToMarkdown({ collection, products: result.products, filters: transformedFilters.filters, priceRange: hasPriceRange ? transformedFilters.priceRange : undefined, + currencyCode, activeFilters, pageInfo: result.pageInfo, locale, diff --git a/apps/template/app/search/md/route.ts b/apps/template/app/search/md/route.ts index 9f08f2d5..a92bf599 100644 --- a/apps/template/app/search/md/route.ts +++ b/apps/template/app/search/md/route.ts @@ -4,6 +4,7 @@ import { buildProductFiltersFromParams, getProducts, } from "@/lib/shopify/operations/products"; +import { getShopDefaultCurrencyCode } from "@/lib/shopify/operations/shop"; import { transformShopifyFilters } from "@/lib/shopify/transforms/filters"; import { RESULTS_PER_PAGE, parseFiltersFromSearchParams, searchParamsToRecord } from "@/lib/utils"; @@ -40,6 +41,7 @@ export async function GET(request: Request) { const transformedFilters = transformShopifyFilters(result.filters, { activeFilters }); const hasPriceRange = result.filters.some((filter) => filter.type === "PRICE_RANGE"); + const currencyCode = result.products[0]?.price.currencyCode ?? (await getShopDefaultCurrencyCode()); const markdown = searchResultsToMarkdown({ query, collection, @@ -47,6 +49,7 @@ export async function GET(request: Request) { total: result.total, filters: transformedFilters.filters, priceRange: hasPriceRange ? transformedFilters.priceRange : undefined, + currencyCode, activeFilters, pageInfo: result.pageInfo, locale, diff --git a/apps/template/app/search/page.tsx b/apps/template/app/search/page.tsx index 8f14ecda..461f2629 100644 --- a/apps/template/app/search/page.tsx +++ b/apps/template/app/search/page.tsx @@ -22,6 +22,7 @@ import type { Locale } from "@/lib/i18n"; import { getLocale } from "@/lib/params"; import { buildAlternates, buildOpenGraph } from "@/lib/seo"; import { buildProductFiltersFromParams, getProducts } from "@/lib/shopify/operations/products"; +import { getShopDefaultCurrencyCode } from "@/lib/shopify/operations/shop"; import { transformShopifyFilters } from "@/lib/shopify/transforms/filters"; import { RESULTS_PER_PAGE, parseFiltersFromSearchParams } from "@/lib/utils"; @@ -202,11 +203,13 @@ async function SearchFilterContent({ const transformedFilters = transformShopifyFilters(result.filters, { activeFilters, }); + const currencyCode = result.products[0]?.price.currencyCode ?? (await getShopDefaultCurrencyCode()); return ( ); diff --git a/apps/template/components/cart/context.tsx b/apps/template/components/cart/context.tsx index 1bade740..c6427f2a 100644 --- a/apps/template/components/cart/context.tsx +++ b/apps/template/components/cart/context.tsx @@ -96,15 +96,23 @@ function computeCartWithPending( } if (pendingQuantity > 0) { + const currencyCode = + pendingLines[0]?.cost.totalAmount.currencyCode ?? + pendingLines[0]?.merchandise.price?.currencyCode; + + if (!currencyCode) { + return null; + } + return { id: undefined, checkoutUrl: "", totalQuantity: pendingQuantity, note: null, cost: { - subtotalAmount: { amount: "0", currencyCode: "USD" }, - totalAmount: { amount: "0", currencyCode: "USD" }, - totalTaxAmount: { amount: "0", currencyCode: "USD" }, + subtotalAmount: { amount: "0", currencyCode }, + totalAmount: { amount: "0", currencyCode }, + totalTaxAmount: { amount: "0", currencyCode }, }, lines: pendingLines, shippingCost: null, diff --git a/apps/template/components/collections/filter-sidebar.tsx b/apps/template/components/collections/filter-sidebar.tsx index e6be2cd7..806e7c3e 100644 --- a/apps/template/components/collections/filter-sidebar.tsx +++ b/apps/template/components/collections/filter-sidebar.tsx @@ -1,6 +1,6 @@ "use client"; -import { useTranslations } from "next-intl"; +import { useLocale, useTranslations } from "next-intl"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { useEffect, useOptimistic, useRef, useState } from "react"; @@ -24,30 +24,64 @@ import { } from "@/components/ui/filter-sidebar"; import { getActiveFilterBadges } from "@/lib/shopify/transforms/filters"; import type { Filter, PriceRange } from "@/lib/types"; +import { getCurrencySymbol } from "@/lib/utils"; interface CollectionFilterSidebarClientProps { filters: Filter[]; priceRange?: PriceRange; + currencyCode: string; activeFilters: Record; } type FilterState = Record; const PRICE_PRESETS = [ - { label: "Under $50", min: 0, max: 50 }, - { label: "$50 - $100", min: 50, max: 100 }, - { label: "$100 - $200", min: 100, max: 200 }, - { label: "Over $200", min: 200, max: undefined }, + { min: 0, max: 50 }, + { min: 50, max: 100 }, + { min: 100, max: 200 }, + { min: 200, max: undefined }, ]; -function formatPriceRangeLabel(min: number | null, max: number | null): string { +function formatCurrencyAmount(amount: number, locale: string, currencyCode: string): string { + return new Intl.NumberFormat(locale, { + style: "currency", + currency: currencyCode, + currencyDisplay: "narrowSymbol", + minimumFractionDigits: Number.isInteger(amount) ? 0 : 2, + maximumFractionDigits: Number.isInteger(amount) ? 0 : 2, + }).format(amount); +} + +function formatPriceRangeLabel( + min: number | null, + max: number | null, + locale: string, + currencyCode: string, +): string { if (min !== null && max !== null) { - return `$${min} - $${max}`; + return `${formatCurrencyAmount(min, locale, currencyCode)} - ${formatCurrencyAmount(max, locale, currencyCode)}`; } if (min !== null) { - return `From $${min}`; + return `From ${formatCurrencyAmount(min, locale, currencyCode)}`; + } + return `Up to ${formatCurrencyAmount(max ?? 0, locale, currencyCode)}`; +} + +function formatPricePresetLabel( + min: number, + max: number | undefined, + locale: string, + currencyCode: string, +): string { + if (max === undefined) { + return `Over ${formatCurrencyAmount(min, locale, currencyCode)}`; + } + + if (min === 0) { + return `Under ${formatCurrencyAmount(max, locale, currencyCode)}`; } - return `Up to $${max}`; + + return `${formatCurrencyAmount(min, locale, currencyCode)} - ${formatCurrencyAmount(max, locale, currencyCode)}`; } function getFilterValues(value: string | string[] | undefined): string[] { @@ -117,8 +151,10 @@ function applyPriceParams(params: URLSearchParams, min: number | null, max: numb export function CollectionFilterSidebarClient({ filters, priceRange, + currencyCode, activeFilters, }: CollectionFilterSidebarClientProps) { + const locale = useLocale(); const pathname = usePathname(); const router = useRouter(); const searchParams = useSearchParams(); @@ -236,7 +272,7 @@ export function CollectionFilterSidebarClient({ ))} {hasPriceFilter && ( - {formatPriceRangeLabel(urlPriceMin, urlPriceMax)} + {formatPriceRangeLabel(urlPriceMin, urlPriceMax, locale, currencyCode)} )} @@ -252,6 +288,7 @@ export function CollectionFilterSidebarClient({ onMinChange={setMinInput} onMaxChange={setMaxInput} onApply={applyPriceRange} + currencySymbol={getCurrencySymbol(currencyCode, locale)} > {PRICE_PRESETS.map((preset) => { @@ -263,11 +300,11 @@ export function CollectionFilterSidebarClient({ return ( applyPricePreset(preset.min, preset.max)} > - {preset.label} + {formatPricePresetLabel(preset.min, preset.max, locale, currencyCode)} ); })} diff --git a/apps/template/components/collections/filters.tsx b/apps/template/components/collections/filters.tsx index 2a73c98c..fd20ff21 100644 --- a/apps/template/components/collections/filters.tsx +++ b/apps/template/components/collections/filters.tsx @@ -14,12 +14,13 @@ async function Render({ }: { collectionResultsDataPromise: Promise; }) { - const { activeFilters, transformedFilters } = await collectionResultsDataPromise; + const { activeFilters, currencyCode, transformedFilters } = await collectionResultsDataPromise; return ( ); diff --git a/apps/template/components/search/results.tsx b/apps/template/components/search/results.tsx index 2fad2e17..6deb8eb2 100644 --- a/apps/template/components/search/results.tsx +++ b/apps/template/components/search/results.tsx @@ -11,6 +11,7 @@ import { ProductCard, ProductCardSkeleton } from "@/components/product-card"; import { Skeleton } from "@/components/ui/skeleton"; import type { Locale } from "@/lib/i18n"; import { buildProductFiltersFromParams, getProducts } from "@/lib/shopify/operations/products"; +import { getShopDefaultCurrencyCode } from "@/lib/shopify/operations/shop"; import { transformShopifyFilters } from "@/lib/shopify/transforms/filters"; import { RESULTS_PER_PAGE } from "@/lib/utils"; @@ -69,6 +70,7 @@ export async function Results({ const transformedFilters = transformShopifyFilters(result.filters, { activeFilters, }); + const currencyCode = result.products[0]?.price.currencyCode ?? (await getShopDefaultCurrencyCode()); const products = result.products; return ( @@ -78,6 +80,7 @@ export async function Results({ diff --git a/apps/template/lib/collections/server.ts b/apps/template/lib/collections/server.ts index c7538e0a..3b55cccf 100644 --- a/apps/template/lib/collections/server.ts +++ b/apps/template/lib/collections/server.ts @@ -3,6 +3,7 @@ import { buildProductFiltersFromParams, getCollectionProducts, } from "@/lib/shopify/operations/products"; +import { getShopDefaultCurrencyCode } from "@/lib/shopify/operations/shop"; import { type TransformedFilters, transformShopifyFilters } from "@/lib/shopify/transforms/filters"; import { RESULTS_PER_PAGE, parseFiltersFromSearchParams } from "@/lib/utils"; @@ -15,6 +16,7 @@ export interface CollectionSearchState { export interface CollectionResultsData { activeFilters: Record; cursor?: string; + currencyCode: string; result: Awaited>; transformedFilters: TransformedFilters; } @@ -57,6 +59,7 @@ export async function getCollectionResultsData({ return { activeFilters, cursor, + currencyCode: result.products[0]?.price.currencyCode ?? (await getShopDefaultCurrencyCode()), result, transformedFilters: transformShopifyFilters(result.filters, { activeFilters, diff --git a/apps/template/lib/i18n/index.ts b/apps/template/lib/i18n/index.ts index 1e117eb1..a3e0c7e5 100644 --- a/apps/template/lib/i18n/index.ts +++ b/apps/template/lib/i18n/index.ts @@ -34,7 +34,8 @@ export function resolveLocale(value: string | null | undefined): Locale { return value && isEnabledLocale(value) ? value : defaultLocale; } -// Currency data per locale +// Explicit locale → currency mappings are only for configured multi-locale +// storefronts. Single-locale pricing should come from Shopify responses. const localeCurrency: Record = { "en-US": { currency: "USD", symbol: "$" }, }; diff --git a/apps/template/lib/markdown/catalog.ts b/apps/template/lib/markdown/catalog.ts index fd7fd21b..3d789d31 100644 --- a/apps/template/lib/markdown/catalog.ts +++ b/apps/template/lib/markdown/catalog.ts @@ -1,4 +1,3 @@ -import { getCurrencyCode } from "@/lib/i18n"; import type { Filter, PageInfo, PriceRange, ProductCard } from "@/lib/types"; import { getActiveFilterBadges } from "@/lib/shopify/transforms/filters"; @@ -24,9 +23,7 @@ function getSingleValue(value: string | string[] | undefined): string | undefine return Array.isArray(value) ? value[0] : value; } -function formatPriceRange(priceRange: PriceRange, locale: string): string { - const currencyCode = getCurrencyCode(locale); - +function formatPriceRange(priceRange: PriceRange, locale: string, currencyCode: string): string { return `${formatPrice({ amount: priceRange.min.toString(), currencyCode }, locale)} - ${formatPrice( { amount: priceRange.max.toString(), currencyCode }, locale, @@ -87,6 +84,7 @@ export function appendAvailableFiltersSection( }: { filters: Filter[]; priceRange?: PriceRange; + currencyCode: string; locale: string; }, ): void { @@ -98,7 +96,7 @@ export function appendAvailableFiltersSection( sections.push(""); if (priceRange) { - sections.push(`- **Price Range**: ${formatPriceRange(priceRange, locale)}`); + sections.push(`- **Price Range**: ${formatPriceRange(priceRange, locale, currencyCode)}`); } for (const filter of filters) { diff --git a/apps/template/lib/markdown/collection.ts b/apps/template/lib/markdown/collection.ts index e45ed0a0..0cd8a553 100644 --- a/apps/template/lib/markdown/collection.ts +++ b/apps/template/lib/markdown/collection.ts @@ -14,6 +14,7 @@ export function collectionToMarkdown({ products, filters, priceRange, + currencyCode, activeFilters, pageInfo, locale, @@ -23,6 +24,7 @@ export function collectionToMarkdown({ products: ProductCard[]; filters: Filter[]; priceRange?: PriceRange; + currencyCode: string; activeFilters: Record; pageInfo: PageInfo; locale: string; @@ -50,7 +52,7 @@ export function collectionToMarkdown({ } appendAppliedFiltersSection(sections, { activeFilters, filters }); - appendAvailableFiltersSection(sections, { filters, priceRange, locale }); + appendAvailableFiltersSection(sections, { filters, priceRange, currencyCode, locale }); appendProductsSection(sections, { products, locale }); appendPaginationSection(sections, pageInfo); diff --git a/apps/template/lib/markdown/search.ts b/apps/template/lib/markdown/search.ts index eb09f22a..d7655579 100644 --- a/apps/template/lib/markdown/search.ts +++ b/apps/template/lib/markdown/search.ts @@ -16,6 +16,7 @@ export function searchResultsToMarkdown({ total, filters, priceRange, + currencyCode, activeFilters, pageInfo, locale, @@ -27,6 +28,7 @@ export function searchResultsToMarkdown({ total: number; filters: Filter[]; priceRange?: PriceRange; + currencyCode: string; activeFilters: Record; pageInfo: PageInfo; locale: string; @@ -50,7 +52,7 @@ export function searchResultsToMarkdown({ sections.push(""); appendAppliedFiltersSection(sections, { activeFilters, filters }); - appendAvailableFiltersSection(sections, { filters, priceRange, locale }); + appendAvailableFiltersSection(sections, { filters, priceRange, currencyCode, locale }); appendProductsSection(sections, { products, locale }); appendPaginationSection(sections, pageInfo); diff --git a/apps/template/lib/shopify/operations/shop.ts b/apps/template/lib/shopify/operations/shop.ts new file mode 100644 index 00000000..9f9b2993 --- /dev/null +++ b/apps/template/lib/shopify/operations/shop.ts @@ -0,0 +1,28 @@ +import { cacheLife, cacheTag } from "next/cache"; + +import { shopifyFetch } from "../client"; + +type PaymentSettingsResponse = { + paymentSettings: { + currencyCode: string; + }; +}; + +export async function getShopDefaultCurrencyCode(): Promise { + "use cache: remote"; + cacheLife("max"); + cacheTag("shop"); + + const data = await shopifyFetch({ + operation: "getShopDefaultCurrencyCode", + query: ` + query getShopDefaultCurrencyCode { + paymentSettings { + currencyCode + } + } + `, + }); + + return data.paymentSettings.currencyCode; +} diff --git a/apps/template/lib/utils/index.ts b/apps/template/lib/utils/index.ts index 70fa8689..c8372aba 100644 --- a/apps/template/lib/utils/index.ts +++ b/apps/template/lib/utils/index.ts @@ -13,6 +13,18 @@ export function formatPrice(amount: number, currencyCode: string, locale: string }).format(amount); } +export function getCurrencySymbol(currencyCode: string, locale: string): string { + return ( + new Intl.NumberFormat(locale, { + style: "currency", + currency: currencyCode, + currencyDisplay: "narrowSymbol", + }) + .formatToParts(0) + .find((part) => part.type === "currency")?.value ?? currencyCode + ); +} + export function parseFiltersFromSearchParams( searchParams: Record, ): Record { diff --git a/packages/plugin/template-rollout-log/2026-04-15-shopify-default-currency.md b/packages/plugin/template-rollout-log/2026-04-15-shopify-default-currency.md new file mode 100644 index 00000000..a35cd72a --- /dev/null +++ b/packages/plugin/template-rollout-log/2026-04-15-shopify-default-currency.md @@ -0,0 +1,43 @@ +--- +title: Use Shopify default currency for single-locale pricing chrome +changeKey: shopify-default-currency +introducedInVersion: 0.1.0 +introducedOn: 2026-04-15 +changeType: fix +defaultAction: adopt +appliesTo: + - all +paths: + - apps/template/components/cart/context.tsx + - apps/template/components/collections/filter-sidebar.tsx + - apps/template/lib/collections/server.ts + - apps/template/lib/markdown/catalog.ts + - apps/template/lib/shopify/operations/shop.ts +relatedSkills: + - /vercel-shop:enable-shopify-markets +--- + +## Summary + +The template no longer hardcodes USD for single-locale search and collection pricing chrome. Filter presets, markdown price ranges, and optimistic cart fallbacks now use the currency Shopify reports for the store or the current product results. + +## Why it matters + +Stores whose Shopify base currency is not USD could show mismatched symbols or markdown output even when storefront prices were otherwise correct. This keeps template-generated price UI aligned with Shopify's default currency. + +## Apply when + +- Your storefront runs in single-locale mode. +- Your Shopify shop currency is not USD. +- Search filters or markdown endpoints showed `$` while Shopify pricing used another currency. + +## Safe to skip when + +- Your shop currency is already USD. +- You already derive filter and markdown currency directly from Shopify. + +## Validation + +- Visit a collection or search page and confirm the price filter symbol matches Shopify's base currency. +- Request `/collections/` or `/search?q=` with `Accept: text/markdown` and confirm price ranges use the same currency as product prices. +- Add a product to cart and confirm any optimistic subtotal placeholder matches the variant currency. From fb7cc8d178a90a6e3af42d9e5da4b4715f3ce055 Mon Sep 17 00:00:00 2001 From: Boris Besemer Date: Wed, 15 Apr 2026 22:45:07 +0200 Subject: [PATCH 2/2] Configure default shop currency fallback --- .../content/docs/getting-started/index.mdx | 3 ++- apps/docs/content/docs/reference/env-vars.mdx | 1 + .../docs/reference/troubleshooting.mdx | 2 +- apps/template/.env.example | 3 +++ apps/template/lib/markdown/catalog.ts | 1 + apps/template/lib/shopify/operations/shop.ts | 27 +------------------ .../2026-04-15-shopify-default-currency.md | 11 ++++---- 7 files changed, 15 insertions(+), 33 deletions(-) diff --git a/apps/docs/content/docs/getting-started/index.mdx b/apps/docs/content/docs/getting-started/index.mdx index b593569c..557e71fe 100644 --- a/apps/docs/content/docs/getting-started/index.mdx +++ b/apps/docs/content/docs/getting-started/index.mdx @@ -57,10 +57,11 @@ Create a `.env.local` file in your project root: ```bash SHOPIFY_STORE_DOMAIN="your-store.myshopify.com" SHOPIFY_STOREFRONT_ACCESS_TOKEN="your-storefront-access-token" +SHOPIFY_DEFAULT_CURRENCY="USD" NEXT_PUBLIC_SITE_NAME="Your Store Name" ``` -You can find the storefront token in **Settings → Apps and sales channels → Headless**. For the full variable list, see [Environment Variables](/docs/reference/env-vars). +You can find the storefront token in **Settings → Apps and sales channels → Headless**. Set `SHOPIFY_DEFAULT_CURRENCY` to your shop currency, such as `GBP`, when it differs from USD. For the full variable list, see [Environment Variables](/docs/reference/env-vars). ## Step 4: Run locally diff --git a/apps/docs/content/docs/reference/env-vars.mdx b/apps/docs/content/docs/reference/env-vars.mdx index 7a549447..3d2f8999 100644 --- a/apps/docs/content/docs/reference/env-vars.mdx +++ b/apps/docs/content/docs/reference/env-vars.mdx @@ -16,6 +16,7 @@ type: reference | Variable | Description | |----------|-------------| +| `SHOPIFY_DEFAULT_CURRENCY` | Base storefront currency for single-locale filter UI and markdown fallbacks, e.g. `GBP` or `USD`. Set this when your shop currency is not USD so empty search/collection states still render the correct symbol. | | `SHOPIFY_STOREFRONT_PRIVATE_TOKEN` | Private Storefront API token for server-side requests. Enables higher rate limits and access to draft content. | | `SHOPIFY_CUSTOMER_ACCOUNT_URL` | Customer Account API URL. Required when using the enable-shopify-auth skill. | | `SHOPIFY_CUSTOMER_CLIENT_ID` | Customer Account API client ID. Required when using the enable-shopify-auth skill. | diff --git a/apps/docs/content/docs/reference/troubleshooting.mdx b/apps/docs/content/docs/reference/troubleshooting.mdx index 50586277..3b263075 100644 --- a/apps/docs/content/docs/reference/troubleshooting.mdx +++ b/apps/docs/content/docs/reference/troubleshooting.mdx @@ -65,7 +65,7 @@ To get near-instant updates, set up Shopify webhooks pointed at `/api/webhooks/s ## Locale or currency not changing -Single-locale storefronts inherit the shop's default Shopify currency for pricing and filter chrome. Multi-locale support still requires Shopify Markets to be enabled. Run [`/vercel-shop:enable-shopify-markets`](/docs/skills/enable-shopify-markets) to add multi-locale and multi-currency routing. +Single-locale storefronts use Shopify pricing data where available, but empty search/collection states rely on `SHOPIFY_DEFAULT_CURRENCY` for filter chrome and markdown fallbacks. Set that env var to your shop currency, for example `GBP`, if your store is not USD. Multi-locale support still requires Shopify Markets to be enabled. Run [`/vercel-shop:enable-shopify-markets`](/docs/skills/enable-shopify-markets) to add multi-locale and multi-currency routing. ## Agent can't find context files diff --git a/apps/template/.env.example b/apps/template/.env.example index b50052b7..0de66f58 100644 --- a/apps/template/.env.example +++ b/apps/template/.env.example @@ -2,5 +2,8 @@ SHOPIFY_STORE_DOMAIN="your-store.myshopify.com" SHOPIFY_STOREFRONT_ACCESS_TOKEN="your-storefront-access-token" +# Optional — default currency used for single-locale filter chrome and markdown fallbacks +SHOPIFY_DEFAULT_CURRENCY="USD" + # Store display name (shown in header, metadata, etc.) NEXT_PUBLIC_SITE_NAME="Your Store Name" diff --git a/apps/template/lib/markdown/catalog.ts b/apps/template/lib/markdown/catalog.ts index 3d789d31..be74d1c8 100644 --- a/apps/template/lib/markdown/catalog.ts +++ b/apps/template/lib/markdown/catalog.ts @@ -80,6 +80,7 @@ export function appendAvailableFiltersSection( { filters, priceRange, + currencyCode, locale, }: { filters: Filter[]; diff --git a/apps/template/lib/shopify/operations/shop.ts b/apps/template/lib/shopify/operations/shop.ts index 9f9b2993..e2ccff7d 100644 --- a/apps/template/lib/shopify/operations/shop.ts +++ b/apps/template/lib/shopify/operations/shop.ts @@ -1,28 +1,3 @@ -import { cacheLife, cacheTag } from "next/cache"; - -import { shopifyFetch } from "../client"; - -type PaymentSettingsResponse = { - paymentSettings: { - currencyCode: string; - }; -}; - export async function getShopDefaultCurrencyCode(): Promise { - "use cache: remote"; - cacheLife("max"); - cacheTag("shop"); - - const data = await shopifyFetch({ - operation: "getShopDefaultCurrencyCode", - query: ` - query getShopDefaultCurrencyCode { - paymentSettings { - currencyCode - } - } - `, - }); - - return data.paymentSettings.currencyCode; + return process.env.SHOPIFY_DEFAULT_CURRENCY ?? "USD"; } diff --git a/packages/plugin/template-rollout-log/2026-04-15-shopify-default-currency.md b/packages/plugin/template-rollout-log/2026-04-15-shopify-default-currency.md index a35cd72a..82929088 100644 --- a/packages/plugin/template-rollout-log/2026-04-15-shopify-default-currency.md +++ b/packages/plugin/template-rollout-log/2026-04-15-shopify-default-currency.md @@ -1,5 +1,5 @@ --- -title: Use Shopify default currency for single-locale pricing chrome +title: Configure single-locale default currency for pricing chrome changeKey: shopify-default-currency introducedInVersion: 0.1.0 introducedOn: 2026-04-15 @@ -19,11 +19,11 @@ relatedSkills: ## Summary -The template no longer hardcodes USD for single-locale search and collection pricing chrome. Filter presets, markdown price ranges, and optimistic cart fallbacks now use the currency Shopify reports for the store or the current product results. +The template no longer hardcodes USD for single-locale search and collection pricing chrome. Filter presets and markdown price ranges now use product currency when available and fall back to `SHOPIFY_DEFAULT_CURRENCY` for empty-result states. ## Why it matters -Stores whose Shopify base currency is not USD could show mismatched symbols or markdown output even when storefront prices were otherwise correct. This keeps template-generated price UI aligned with Shopify's default currency. +Stores whose Shopify base currency is not USD could show mismatched symbols or markdown output even when storefront prices were otherwise correct. This adds an explicit config path so template-generated price UI stays aligned with the storefront's intended default currency. ## Apply when @@ -33,11 +33,12 @@ Stores whose Shopify base currency is not USD could show mismatched symbols or m ## Safe to skip when -- Your shop currency is already USD. +- Your shop currency is already USD and you are fine with the default. - You already derive filter and markdown currency directly from Shopify. ## Validation -- Visit a collection or search page and confirm the price filter symbol matches Shopify's base currency. +- Set `SHOPIFY_DEFAULT_CURRENCY` to your shop currency in `.env.local` or your deployment environment. +- Visit a collection or search page and confirm the price filter symbol matches your configured currency. - Request `/collections/` or `/search?q=` with `Accept: text/markdown` and confirm price ranges use the same currency as product prices. - Add a product to cart and confirm any optimistic subtotal placeholder matches the variant currency.