Why I start every architecture debate with the merchant’s editing workflow
As someone who builds both Hydrogen headless storefronts and custom Liquid themes, I am often the first person in the room to talk a merchant out of going headless. When a merchandising team relies on the Shopify Theme Editor to launch weekly campaigns, rearrange blocks, and test App Blocks without waiting on a developer sprint, a well-architected Online Store 2.0 Liquid theme is hard to beat.
In my practice, choosing between Liquid and headless comes down to editorial autonomy, third-party app compatibility, integration complexity, and long-term maintenance budget—never vanity hype or revenue cliches.
How I prevent race conditions in the Section Rendering API
Shopify’s Section Rendering API lets a theme fetch pre-rendered HTML fragments from the server when a shopper toggles collection filters, switches product variants, or updates the cart drawer. However, in the wild I frequently find themes that break when a user clicks three filter checkboxes rapidly: responses arrive out of order, or a partial 200 OK payload returns `null` for a requested section ID and wipes out the product grid.
To make partial updates rock-solid, I use a closure-based request loader that aborts in-flight requests via `AbortController`, tracks a monotonic request counter, enforces same-origin safety, and verifies that every requested section returned a valid HTML string before touching the DOM.
- 01Filter
- 02Fetch sections
- 03Validate & update
- 04URL + announcement
export function createSectionLoader() {
let controller;
let latest = 0;
return async (pageUrl, sectionIds) => {
const request = ++latest;
controller?.abort();
controller = new AbortController();
const url = new URL(pageUrl, location.href);
if (url.origin !== location.origin) throw new Error("Expected same origin");
url.searchParams.set("sections", sectionIds.join(","));
const response = await fetch(url, {signal: controller.signal});
if (!response.ok) throw new Error("Section request failed");
const sections = await response.json();
if (request !== latest) return null;
for (const id of sectionIds) {
if (typeof sections[id] !== "string") throw new Error("Section unavailable");
}
return sections;
};
}My race-condition-safe Section Rendering request helper with AbortController cancellation and fragment validation.
How I finish the interaction beyond the network fetch
- I set `aria-busy="true"` on the product grid while loading and keep the user’s active filter controls responsive.
- I validate all returned HTML fragments before swapping any DOM nodes, and I silently ignore `AbortError` cancellations from superseded clicks.
- I update `history.pushState` only after a successful DOM swap, and I wire `popstate` so the browser Back button restores the previous filter state cleanly.
- I restore keyboard focus to a logical element if the previously focused node was replaced, then announce the updated product count via an `aria-live` region.
- If a network fetch fails, I keep the existing products on screen and fall back gracefully to standard full-page navigation.
Sources & review
Written from my field experience building and auditing Shopify themes. Unverified GMV or speed comparisons remain excluded; the Section Rendering loader below demonstrates request-layer cancellation and validation.
Content reviewed 2026-10-25. Written in the first person by Mifan Twan Ardana. No production client benchmark is claimed.