Skip to content

Configured Commerce Upgrade Watch: May 2026

sts 5.2.2605.600+sts Covers May 2026

Written by Lance Farquhar June 1, 2026

Release 5.2.2605.600+sts, published May 29, 2026. Each month we read what Optimizely says shipped, then go verify it against the actual source — frontend and backend — so you get more than a changelog recap.

The 30-Second Version

  • Headline feature: Spire CMS pages now carry real Cache-Control, ETag, and Last-Modified headers end to end, and the storefront short-circuits with a bare 304 when nothing's changed.
  • Worth knowing: the whole platform moved to .NET 10 this release, supported through November 2029 — custom extensions need to retarget net10.0 before you upgrade.
  • One more notable item: Azure AD B2C single sign-on landed for .NET Core storefronts, and Spreedly is being phased out as a standalone payment gateway in favor of Payment Service.
  • Our verdict: every frontend item we could check matched its description closely — and sitting in the same diff, with no mention anywhere in the notes, is a fully wired Apple Pay checkout.

The Headline: Spire CMS Pages Finally Get Real Cache Headers

Spire CMS pages have always been served fresh, every time — the Node/Express storefront ran with its own ETag generation switched off (app.disable("etag")), and nothing from the content API's caching metadata made it into the response. That meant no conditional requests, no CDN-level page caching, and no way for a browser or edge cache to know a page hadn't changed since the last visit.

May's release closes that gap. start.js drops the app.disable("etag") call, and PageRenderer.tsx now forwards the content API's own cache headers straight through to the browser — and if the upstream response is already a 304, the storefront doesn't bother re-rendering the page at all:

Spire CMS page caching sequence
// PageRenderer.tsx
setHeaderIfExists("Cache-Control");
setHeaderIfExists("ETag");
setHeaderIfExists("Last-Modified");
if (pageByUrlResponse.status === 304) {
    response.status(304).end();
    return;
}

Three headers forwarded, one status check, one early return — that's the entire mechanism. It's consistent with the release notes' "configurable shared max-age" description: the actual cache duration is set upstream, and this change is what lets that setting finally reach the browser and any CDN sitting in front of it.

What Else Shipped

.NET 10 upgrade. The whole platform moved off .NET 8 this release. WebApp.props retargets from net8.0 to net10.0, all four Docker builds (Admin.Api, Database.Updater, Integration.Api, Storefront.Api) update their build commands to match, and tools/allowedLibraries.netcore.json picks up a batch of new .NET 10 BCL assemblies. It's a clean, one-shot change landing exactly at this release, with support running through November 2029. If you maintain custom extensions, they need to target net10.0 starting with 5.2.2605.

Five more breaking changes, all low risk:

  • DefaultFileSystemProvider and FileSystemProvider moved off the public API surface.
  • A set of long-deprecated SiteContext properties (Website, UserProfile, Language, Currency, Warehouse, Persona, RememberedUserProfile) were removed outright.
  • BaseApiController.Request was removed — a binary-breaking change for anything referencing it directly.
  • IPaymentService and IPaymentGateway both gained three new methods for Buy-Now-Pay-Later support: HandleBnplCallbackAsync, GetBnplTransactionStatus, and HandleBnplRedirect.
  • If you're upgrading from 5.2.2602.424, 5.2.2603.389, or 5.2.2604.558 and have ALL CAPS attribute names with spaces or special characters, a full Search Index rebuild is required.

Confirmed and checked out cleanly on the frontend:

  • The Stripe Radar fraud-detection session now loads once, globally, instead of per checkout component — detail below.
  • Third-party URL parameters (UTM tags, affiliate codes) are preserved on Spire product list and search pages instead of being silently stripped — detail below.
  • The newsletter subscription checkbox on the Create Account page now pre-populates from the guest's existing account record instead of always defaulting.
  • The "Remove All" cart action now fires an ODP remove_from_cart event for every line before the cart clears.
  • The Budget Management page's dropdown-disabled logic was reworked to account for all three enforcement levels (Customer, ShipTo, User) instead of a two-way check.
  • Variant product search by CustomerProductNumber now falls back to the typed value and opens a variant-selector modal instead of doing nothing when there's no exact match.

Aligned nginx upload size to 120 MB — the reverse-proxy config picks up a large_upload flag on the relevant routes, consistent with the described change, though the actual 120 MB figure is set downstream of this repo and worth confirming directly if you rely on large uploads.

Everything Admin Console, Classic .NET, and Payment/Search Service — Custom Impersonation Roles, the Max Discount Amount and Order Bill To Customer List promotion options, cross-sell sort ordering, read-only Spire CMS data pages in Admin Console, audit logging for Payment and Search services, the Proactive Query Analyzer's CPU-ranked findings, database-lock diagnostics, Spreedly's removal as a standalone gateway, and a long list of related bug fixes among them — sits outside what this cycle's source check could cover. If you lean on any of it, it's worth testing directly against a running instance.

What We Actually Found: Apple Pay Checkout, Unannounced

Every post in this series, we go looking for something the release notes don't mention. This month it's a full payment method.

The diff adds a 214-line ApplePayButtonWrapper.tsx, a useApplePaySdk.ts hook, a new ApplePayConfig type with getApplePayConfig() and validateApplePayMerchant() API calls, a LoadApplePayConfig.ts handler, and enableApplePay flags threaded through CartSettingsModel and both payment-details hooks, plus a global ApplePaySession type declaration. Alongside it, both routes.yaml and spire_routes.json add a new /.well-known route — the standard location for the domain-association file Apple requires before it'll let a site take Apple Pay. None of this — not the button, not the config plumbing, not the routing change — appears anywhere in the published release notes.

If you support checkout on iOS Safari or macOS Safari, this is worth turning on and testing directly rather than waiting for it to show up in a future changelog.

A second, smaller thread pairs naturally with the caching work above: SessionLoader.tsx picks up a calculateCustomerSegmentAfterLoadingPage setting and a checkDeferredPersonas() method that defers persona/customer-segment calculation until after the initial page load, then reloads the page and header/footer only if that calculation disagrees with what the server originally sent. Deferring persona-specific logic is exactly what you'd want if you're trying to make more of a Spire page cacheable — which is precisely what this release is doing elsewhere.

Under the Hood

The Stripe Radar session moved from per-component to page-level, in one small handler chain. The old useStripeSession.ts hook is gone, replaced by a chain dispatched once from the page-init lifecycle:

// SetStripeScript.ts
export const chain = [LookUpData, LoadStripeScript, CreateStripeRadarSession];
export const setStripeScript = createHandlerChainRunnerOptionalParameter(chain, {}, "SetStripeScript");

Instead of every checkout component that touches a credit card field independently loading Stripe's script and opening its own Radar session, the session is created once per page load and shared from there.

The third-party URL parameter fix is an allowlist plus a merge, not a rewrite. SetFilterQuery.ts now checks incoming query keys against a knownFilterKeys list (paging, sort, brand/category/price filters) and folds anything else back in before re-serializing:

// SetFilterQuery.ts
const mergedQuery = { ...queryObject, ...thirdPartyParams };
props.result = qs.stringify(mergedQuery, { arrayFormat: "comma", skipNulls: true });

Previously the handler rebuilt the query string from its own known keys alone, which meant anything Spire didn't recognize — UTM parameters, affiliate tags — quietly disappeared on the way through. Now those keys ride along untouched.

What This Means for You

  • If you maintain custom extensions, retarget them to net10.0 before taking 5.2.2605 — this is the one change in this release that touches everyone.
  • If you've implemented DefaultFileSystemProvider, FileSystemProvider, BaseApiController.Request, or any of the removed SiteContext properties, check your code before upgrading — these are removals, not deprecations, as of this release.
  • If you support BNPL payment providers, IPaymentService and IPaymentGateway both need the three new methods implemented.
  • If you're coming from 5.2.2602.424, 5.2.2603.389, or 5.2.2604.558 and use ALL CAPS attribute names with spaces or special characters, plan for a full Search Index rebuild.
  • If you're on Spreedly as a standalone gateway, start planning the move to Payment Service.
  • If your storefront sits behind a CDN or relies on browser caching for Spire CMS pages, this release is what makes shared max-age and conditional requests actually work — worth revisiting your cache configuration.
  • If you accept payments on Safari/iOS, go turn on Apple Pay and test it — it's there, and the release notes won't tell you.
  • Everything else on the frontend — Stripe Radar, the URL parameter fix, the ODP event, the newsletter checkbox, variant search — is safe to take as-is; we checked, and it does what it says.

Upgrading a Configured Commerce storefront and want a second set of eyes on what actually changed under a release before you take it? That's exactly the kind of review Nishtech does for clients every month — reach out and we'll walk through it with you.