Layout Shell Rewritten as a Load-Order Contract: Origin Hints, Two Stylesheet Tiers and an Interaction-Gated Session Recorder
Dawn 6.0.2's theme.liquid reworked so that the order things load in is written down: connection hints before the title, stylesheets sorted into a blocking tier and a deferred tier, fonts preloaded, and a session-recording script that waits for the shopper to move.
A Shopify layout emits whatever its sections and apps ask for, in the order they ask. On Nudestix that meant a dozen vendor scripts and two CSS layers arriving together. We rewrote Dawn 6.0.2's theme.liquid as a load-order contract — origin hints ahead of the title, two stylesheet tiers, woff2 font preloads gated on the font picker, and session recording held behind four interaction listeners and an eight-second timer.
The Problem
A theme's layout file is the one place the whole document gets assembled, and by default it assembles in arrival order. Installed apps register their tags through the platform's head content; each section brings its own <link> and <script>; the theme's stylesheets sit wherever the theme author left them. Nothing in that arrangement records which files the page needs in order to lay out and which it doesn't.
Nudestix carries a large vendor stack — a tag manager, a consent manager, session recording, email, loyalty, reviews, wishlist, buy-now-pay-later and affiliate tracking — on top of a Tailwind layer written over Dawn's own CSS. Left to arrival order, all of it is requested alongside the two stylesheets the page can't lay out without. The work here was to write the order down as a contract in the layout.
The Constraint
Dawn 6.0.2's layout expects a small stylesheet set and a single global.js. Everything the store had added since — the Tailwind output, the Nudeskin sub-brand sheet, and a set of third-party stylesheets — had arrived as extra <link> tags with no tiering between them.
The vendor stack was not up for removal. Every one of those scripts is a live integration a marketing team relies on, so the options were when and how, never whether. The theme editor re-runs section scripts on every section reload, so nothing in the layout could assume it executes exactly once. And a shopper with JavaScript off still had to get a styled page, which rules out any deferral pattern that has no fallback. Two sub-brands share the layout, so whatever it emits has to be addressable per brand from one file.
What We Built
Ahead of <title>, theme.liquid emits a block of preconnect and dns-prefetch hints for the origins the page is certain to touch: the Shopify CDN and font CDN, the tag manager and analytics hosts, Typekit, the session recorder, Shopify's own telemetry host, the social pixel and an error-monitoring service. The font-CDN hint is itself wrapped in a check on the font-picker settings, so a system-font store doesn't open a connection it has no use for.
Stylesheets are sorted into two tiers by a single question — can the page lay out without this file? tailwind-output.css and Dawn's base.css can't be spared, so they're emitted through stylesheet_tag with preload: true and load the ordinary blocking way. The theme's own remaining sheets — custom.css, fonts.css and nudeskin.css — go out as rel="preload" with onload="this.rel='stylesheet'", each with a <noscript> twin carrying a plain stylesheet link. The body and heading faces are preloaded as woff2, skipped when the merchant has picked a system font.
The session-recording script sits behind an interaction gate. Its loader is defined inside a DOMContentLoaded handler and bound to mousemove, touchstart, scroll and keydown with { passive: true, once: true }; the handler that fires first also removes the other three listeners. A setTimeout of eight seconds calls the same loader. The vendor's own snippet sits inside that loader, so nothing is requested from the recorder's origin until something calls it.
Below the gate, the layout publishes what the theme's JavaScript expects to find: window.routes, cartStrings, variantStrings and a serialized cartJson. A global IntersectionObserver swaps data-src into src for any .intersect-ob image, with a -200px root margin on the bottom edge, so a tagged image is requested once it's a couple of hundred pixels inside the viewport rather than on approach.
The body class list carries template- and template-name- classes and aliases the older nudebody name to nudeskin; an inline script adds nudestix-product or nudeskin-product, derived from product.type, the collection's template_suffix and the template name, so both sub-brands are styled from one layout. A mobile-only script scrolls [data-main-pro-title] into view on a shopper's first visit to a product page and stores the handle in sessionStorage so it doesn't repeat.
Why This Way
The tiering is one decision per file, and it's the same decision every time: does layout depend on it? The Tailwind output and Dawn's base sheet — yes, so they block, the way a stylesheet normally does. The sub-brand override sheet and the theme's fonts.css — no, so they go out as a preload that the onload handler promotes to a stylesheet, with a <noscript> twin covering the visitor for whom that handler never runs.
Gating the recorder on interaction is a statement about what the script is for. A session recorder exists to capture sessions with something in them, and a visitor who hasn't moved yet hasn't produced one; the eight-second timer covers the visitor who reads without moving.
We took on two things by doing it this way. The deferred tier means a flash of unstyled content is possible for any element whose only styling lives in a tier-two file, so the assignment of files to tiers has to be revisited whenever a rule moves between stylesheets. And because the policy is a file rather than a dashboard, nothing enforces it when the next section adds a <link> of its own — the order is explicit and reviewable, and it's also re-decided by hand.
Why Not an App
Speed-optimizer apps do a version of this from outside the theme: they read the page the platform served and rewrite it — deferring scripts, adding hints, lazy-loading images — by rules that apply generically to whatever they find. That's a legitimate way to get the behavior without touching a line of Liquid, and for a store without a developer on hand it's often the right call.
Doing it in the layout makes the load order a thing you can read. Each deferral is a decision about a named file, visible in a diff, arguable and revertible, versioned with the theme. An app's rewrite applies to sections added after it was configured and lives in the vendor's dashboard rather than the repository. Measurement is separate work either way — it is what would decide between the two, and neither route supplies it.
Implementation Notes
- The origin hints are emitted ahead of
<title>, and the font-CDNpreconnectis wrapped in a check on both font-picker settings. - Tier-one sheets use Shopify's
stylesheet_tagfilter withpreload: true, which emits the<link rel="preload">and the stylesheet link together; tier-two sheets are hand-writtenpreload/onloadmarkup so a<noscript>twin can sit beside each one. - The four interaction events are bound with
{ passive: true, once: true }, and the first handler to run removes the remaining three; the eight-second timer calls the same loader function. - Font preloads are wrapped in a check on
type_body_font.system?, so a merchant who picks a system font doesn't get a preload for a file no rule references. - Predictive-search CSS and its three scripts are emitted only when
settings.predictive_search_enabledis on. - The restock-notification snippet is rendered only after the layout finds at least one unavailable variant, and the loop breaks on the first hit.
- Afterpay's script is emitted from the layout on every template except the home page, and once more inside the cart drawer snippet so the drawer can render its messaging.
- The
shopify-design-modeclass is added to<body>whenShopify.designModeis true, so sections can behave differently inside the theme editor.
Edge Cases
- JavaScript off: the two tier-one sheets load the ordinary way, and each
preload/onloadsheet carries a<noscript>link to the same file, so the swap is a refinement over a stylesheet the browser would have fetched anyway. - System font chosen in theme settings: no font preloads are emitted, and the
fonts.csssheet still loads in tier two. - A visitor who never scrolls, taps or types: the eight-second timer calls the loader anyway, so a still reader is not simply absent from the recorder.
- Inside the theme editor:
Shopify.designModeadds ashopify-design-modeclass to the body, so sections that re-run on reload can tell an editor render from a storefront one. - Second product visit in the same session: the mobile scroll-to-title script finds the handle in
sessionStorageand does nothing, so the page doesn't jump twice. - Predictive search disabled: none of its CSS or scripts are emitted, so the setting removes the assets rather than hiding a loaded feature.
Platform Primitives Used
- Liquid section schema and settings — the layout reads
predictive_search_enabledand the font-picker settings to decide what to emit at all. - Deferred CSS loading — the tier-two pattern:
preload, anonloadswap tostylesheet, and a<noscript>link to the same file. - Intersection Observer API — the global
data-srctosrcswap for.intersect-obimages.
Integrations in Play
- Hotjar — session recording, loaded only through the interaction gate or its eight-second fallback.
- Afterpay — buy-now-pay-later messaging, emitted per template from the layout and again inside the cart drawer.
- Tailwind —
tailwind-output.cssis one of the two tier-one stylesheets; the layout treats it as render-critical. - Klaviyo, LoyaltyLion, PowerReviews, Wishlist Hero, Google Tag Manager and Osano — the rest of the stack the layout hosts. They coexist with the contract; the layout decides ordering around them, not whether they run.
Where It Runs
Everywhere. The layout wraps every template on the storefront — home, collection, product, cart, pages, blog and account — so the origin hints, the two tiers and the interaction gate apply to every page view, and a change to any of them lands on the whole store at once.
What This Demonstrates
- Third-party script governance — the primary capability: a layout that decides, file by file and vendor by vendor, what the document asks for and when, including a recorder whose loader is not called at parse time.
- Media and asset loading strategy — the asset half of the same file: two stylesheet tiers,
woff2preloads conditioned on the font picker, and adata-srcobserver for tagged images.
How We Know
The layout file itself, roughly 330 lines, read from the live theme, plus six client task-register entries covering this thread of work. The records establish that the interaction gate on the recorder and the image observer were scheduled and delivered rather than incidental; the stylesheet tiering and the origin-hint block are documented from the code. Confidence is strong. None of the records carries a measurement and we don't attach one — the claim is that the order is written down, not that writing it down changed anything.
Related Builds
- Font delivery and a template-conditioned image preload in a licensed theme's layout — Three Ships' layout doing the same job on a licensed theme, where the emphasis lands on fonts and one conditional image preload.
- Mega-menu product rail (v2): a library-free slider in a theme asset, constructed per panel when the panel is seen — a component inside this layout that decides its own construction time rather than relying on the layout to defer it.
- DE Hero: art-directed image/video hero with layout controls — LUS Brands, where the same loading decisions are pushed into the sections instead of concentrated in the layout.
- Deferred LyveCom shoppable-video mounting on the product page — one vendor script, one memoized promise: the section-scale version of the gate above.
The Buy-vs-Build Question
A speed app buys generic deferral rules applied from outside, configured in a dashboard and kept current by the vendor. Building it into the layout bought an explicit, reviewable order and cost a policy that lives in one file with nothing enforcing it. Which side you land on is argued at site speed optimization apps: buy or build?, and the vendor-script half of it at third-party script governance: buy or build?.
Provenance & Evidence
- Client: Nudestix — nudestix.com
- Surface: Global — the layout wraps every template
- Templates served: all of them
- Complexity: High
- Scale: roughly 330 lines in one layout file
- Attribution: Deploi-modified vendor. Built on top of Dawn 6.0.2; this layout is our modification of Dawn's
theme.liquid, not a layout we wrote from nothing. The origin hints, the stylesheet tiers, the font preloads, the interaction gate and the image observer are ours; Dawn's document structure and itsglobal.jscontract remain the vendor's. - Status: Live, verified 2026-09-07
- Evidence: one layout file read from the theme, plus six client task-register records
- Confidence: Strong — the records document the deferral work as scheduled and delivered; the tiering is documented from the code
- Not claimed: any timing, score, before-and-after or qualitative speed statement about this storefront
- Primary capability: Third-party script governance
Ready to Write Down What Your Theme Loads First?
If nobody on your team can say which stylesheet your storefront can't lay out without, or which vendor script runs before the shopper has touched anything, the answer is currently "whatever arrived first." Contact us today and we'll read your layout with you — and if you want a number out of the exercise, we'll talk about measuring before we talk about changing anything.