> This page is for Developer.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.bigcommerce.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.bigcommerce.com/_mcp/server.

# Catalyst version 1.11.0 release notes

> Catalyst 1.11.0 resolves locale subfolders at runtime, adds wallet payment buttons to the cart page, and fixes session, consent, and caching issues.

Catalyst 1.11.0 moves merchant-configured locale subfolders from build time to runtime, renders wallet payment buttons on the cart page, and fixes a set of session, consent, and caching issues. The locale change removes `i18n/locales.ts` and makes `redirect` and `permanentRedirect` async, so read the migration steps before upgrading. For a full list of Catalyst changes, see the [changeset on GitHub](https://github.com/bigcommerce/catalyst/blob/main/core/CHANGELOG.md#1110).

## Locale subfolders resolve at runtime

Catalyst now reads merchant-configured locale subfolders from BigCommerce at runtime instead of baking them into `build-config.json` during `next build`.

Previously, if the control panel returned incomplete locale data at build time, every localized URL returned a 404 until the next deploy. next-intl treats a custom subfolder as a *replacement* for the bare locale code rather than an alias, so there was no fallback: a path such as `/es-es` simply did not match any route.

* **Runtime locale configuration** — the new `i18n/locale-config.ts` reads locale configuration from BigCommerce and caches it in KV with the same stale-while-revalidate pattern as `proxies/with-routes.ts`. An empty locale list is never cached.
* **Locales are no longer written to `build-config.json`** — runtime is the only source. A warm cache rides out a BigCommerce outage. Only a cold cache combined with an unreachable API cannot resolve, and that case returns `503` with `retry-after` rather than a 404 that would tell crawlers the pages are gone.
* **One resolution per request** — `proxies/with-intl.ts` builds its next-intl middleware per request, resolves the configuration once, and forwards it to the render as `x-bc-locale-routing`. Rendering and redirects reuse exactly what resolved the inbound URL, so the two can't disagree and there is no extra round trip. The matched subfolder is passed as `x-bc-locale-prefix`, which `proxies/with-routes.ts` strips instead of recomputing.
* **Client navigation agrees with the proxy** — `Link`, `useRouter`, and `usePathname` read the runtime configuration through a new provider in `app/[locale]/layout.tsx`. Canonical and hreflang URLs in `lib/seo/canonical.ts` and the header locale switcher do the same.
* **`generateStaticParams` removed from `app/[locale]/layout.tsx`** — it only added a build-time dependency on the locale list. Every route under `[locale]` already renders on demand because the tree reads cookies, and the build's route rendering modes are unchanged.

Locale detection itself is unchanged. A shopper is still redirected to their language's subfolder, and an explicit choice in the locale switcher still wins on later requests.

### Migration

* **`redirect` and `permanentRedirect` moved and are now async** — import them from `~/i18n/navigation-server` instead of `~/i18n/routing`, and `await` them. This keeps the GraphQL client and KV out of the client bundle.
* **`i18n/locales.ts` is removed** — the locale gates in `i18n/request.ts` and `app/[locale]/layout.tsx` now use the runtime list. The sitemap, robots, and favicon routes resolve the default channel directly instead of routing through a locale.

> **Note**
>
> Channel-per-locale stores see a behavior change: `robots.txt`, the sitemap index, and the favicon now resolve the default channel directly rather than through the default locale, because they run outside the proxy and have no request locale. Stores that map their default locale to a non-default channel in `channels.config.ts` will see those three served from `BIGCOMMERCE_CHANNEL_ID`.

### Fixed along the way

* `/xmlsitemap.php` and `/admin` redirected through the locale-aware helper, resolving to `/<locale>/sitemap.xml` and `/<locale>/` whenever every locale carried a prefix. The sitemap target was a 404, since `/sitemap.xml` is excluded from the proxy. Both now use plain redirects, and `/admin` no longer performs an uncached GraphQL request on every hit to a route that is disabled by default.
* Losing the KV cache no longer takes the storefront down. A read failure degrades to a fetch instead of being treated as unresolvable.
* Locale configuration is validated where it is fetched, so an unusable value can no longer be cached and then rejected on every read. Subfolders are normalized (surrounding slashes and whitespace trimmed), and locale codes and prefixes are constrained to safe URL shapes.
* An unrecognized locale returns 404 rather than 500 when no message file exists for it.
* A locale whose configured subfolder cannot be expressed in a URL is skipped individually, with an error logged, instead of making the whole configuration unusable.
* The Playwright URL fixtures asserted `/<locale-code>/...` instead of the configured subfolder, so alternate-locale assertions were wrong for any store whose subfolder differs from its locale code (for example, `de` served at `/de-de`). They now resolve the subfolder from the store.

## Wallet payment buttons on the cart page

Catalyst now renders wallet payment buttons, such as PayPal, on the cart page when payment wallets are configured for the cart.

* **New cart queries** — `getPaymentWallets`, `getPaymentWalletWithInitializationData`, and `getCurrencyData` in the cart's `page-data.ts` fetch the configured wallets, their initialization data, and currency formatting details.
* **`ClientWalletButtons`** (`core/components/wallet-buttons`) streams wallet initialization options and renders a container per wallet button.
* **`WalletButtonsInitializer`** (`core/lib/wallet-buttons`) lazily injects the BigCommerce Checkout SDK loader script and initializes each wallet button against the `/graphql` endpoint, raising an `InitializationError` when the loader is missing.
* **GraphQL proxy support** — `with-graphql-proxy.ts` and `proxy.ts` now handle Checkout SDK wallet-button requests.
* **`NEXT_PUBLIC_CHECKOUT_SDK_DEV_URL`** was added to `.env.example` to optionally override the Checkout SDK loader URL in development.

End-to-end coverage in `wallet-buttons.spec.ts` verifies that the loader script and wallet button containers render only when wallets are configured.

## Storefront fixes and improvements

* **Available-to-sell quantity on the PDP** — the PDP now shows an error message and disables Add to Cart when the requested quantity exceeds available-to-sell (on-hand plus backorder allowance).
* **Cart "ready to ship" message** — this message now appears only when part of the line item is also backordered. Previously it appeared whenever `showQuantityOnHand` was enabled and any quantity was on hand, even for fully in-stock items where it added no useful information.
* **Cookies on stores with consent disabled** — c15t grants every consent category client-side when consent is disabled, but only in its in-memory store, so no consent cookie was written and server-side checks treated the shopper as having declined. Server-side consent checks now fall back to the store's cookie-consent setting when no consent cookie is present, so the analytics proxy starts visits on the first request and the selected currency persists.
* **Session cookie deletion after logout** — `stripSessionCookieExpiry` stripped `Expires` from all session token `Set-Cookie` headers, including deletion directives, turning cookie deletions into permanent empty-value session cookies that accumulated across login/logout cycles. Deletion directives are now left intact.
* **Product `og:image`** — `ProductPageMetadataQuery` requested `urlTemplate`, which returns a URL containing a literal `{:size}` placeholder that the `<Image>` CDN loader substitutes at render time. `generateMetadata` has no such loader, so the placeholder was emitted verbatim into the Open Graph tag.

  In `core/app/[locale]/(default)/product/[slug]/page-data.ts`, update the `defaultImage` selection to request a concrete URL:

  ```diff
    defaultImage {
      altText
  -   url: urlTemplate(lossy: true)
  +   url(width: 1200, lossy: true)
    }
  ```

  `width: 1200` matches the Open Graph and `summary_large_image` recommendation. Height is omitted intentionally, because the Stencil resizer fits the image inside the given box rather than cropping it.
* **State/Province on registration** — Account Registration validated State/Province as required for countries that have no states, such as Algeria. The register page now queries per-country state data from BigCommerce and hides the field entirely when the selected country has no states.
* **In-memory KV expiry** — entries in the per-process `MemoryKvAdapter` that sits in front of the shared adapter (Cloudflare KV, Upstash, or Vercel Runtime Cache) now expire after 60 seconds. They previously never expired, so once a process had seen a key it stopped consulting the shared store for it and refetched from the origin on its own clock rather than picking up a value another process had already shared. Cached data was never wrong, but origin requests and cache writes scaled with process count. Capacity also increases from 500 to 4096 entries, since cache keys include the query string and accumulate faster than the number of real paths suggests.
* **Translations** — static translations have been updated.

## Release tags

* [**@bigcommerce/catalyst-core@1.11.0**](https://github.com/bigcommerce/catalyst/releases/tag/%40bigcommerce%2Fcatalyst-core%401.11.0)
* [**@bigcommerce/catalyst-client@1.0.3**](https://github.com/bigcommerce/catalyst/releases/tag/%40bigcommerce%2Fcatalyst-client%401.0.3)
* [**@bigcommerce/create-catalyst@2.0.4**](https://github.com/bigcommerce/catalyst/releases/tag/%40bigcommerce%2Fcreate-catalyst%402.0.4)

Latest stable:

* [**@bigcommerce/catalyst-core@latest**](https://github.com/bigcommerce/catalyst/releases/tag/%40bigcommerce%2Fcatalyst-core%40latest)
* [**@bigcommerce/catalyst-makeswift@latest**](https://github.com/bigcommerce/catalyst/releases/tag/%40bigcommerce%2Fcatalyst-makeswift%40latest)