Font Delivery and a Template-Conditioned Image Preload in a Licensed Theme's Layout
Three Ships' theme.liquid decides its own font delivery on a licensed theme — the picked faces preloaded, an Adobe Fonts @font-face block inlined with its loader demoted behind it — and emits one image preload on exactly one template type, under an explicit condition.
A hosted webfont service hands a theme a stylesheet on its own origin, so the browser learns the font file URLs only after fetching it. On Three Ships' licensed-theme layout, Adobe Fonts' ten generated @font-face rules are inlined with font-display: swap, the service stylesheet is demoted to a preload with an onload swap and <noscript> twin, the picked faces are preloaded, and collection templates alone get a fetchpriority="high" preload for the first product image.
The Problem
Shopify's font_picker settings give a theme a font_url it can preload directly. A brand typeface licensed through a hosted service doesn't arrive that way: it arrives as a <link rel="stylesheet"> on the service's origin, and the font file URLs live inside that CSS rather than in the document.
Images have a different version of the same gap. Liquid renders top-down and a layout has no idea which image a template is about to show; a <link rel="preload"> for "the first product image" can only be written as a condition on the request and the collection object, and a preload for an image the page never renders is a request for nothing.
Three Ships sets its type in a compressed family licensed through Adobe Fonts, and its collection pages are where campaign traffic lands. Both gaps sat in one file, theme.liquid, on a theme the brand licenses rather than owns.
The Constraint
The theme is Palo Alto 5.8.0 by Presidio Creative, vendor code kept close to upstream, so the work had to be additive: tags placed around the vendor's, not a rewritten head. The theme's own font_picker faces stay, and any preload for them has to respect a merchant choosing a system font in theme settings, which is what type_base_font.system? exists to report.
Adobe Fonts sets the second boundary. The service's stylesheet isn't the theme's to host and the font files stay on use.typekit.net; what the layout can reproduce is the @font-face block the service generates, with every src still pointing at Adobe's origin. So the declarations could move; the files couldn't.
For the image, the condition had to be expressible in Liquid at render time — request.page_type and collection.products.first.featured_image are the facts the layout has — and the preload's candidate list has to be written by hand, because a <link> has no image_tag filter to lean on.
What We Built
The head carries the font preloads the theme can know about. settings.type_base_font | font_url and settings.type_heading_font | font_url are each emitted as <link rel="preload" as="font" type="font/woff2" crossorigin>, and each is wrapped in {% unless settings.type_base_font.system? %} or its heading twin, so a system-font choice emits nothing. Ahead of them, the origin hints are cut to three: a preconnect to the Shopify CDN and dns-prefetch for the tag manager and Klaviyo, with a longer list for other vendors retained in the file as a comment rather than as markup.
The Adobe Fonts block is emitted after the footer section group, not in the head. Two preconnect hints open use.typekit.net and p.typekit.net. The service stylesheet follows as rel="preload" as="style" with onload="this.onload=null;this.rel='stylesheet'", so it downloads without applying until the swap. Then a <style> block declares the family itself: ten @font-face rules for span-compressed, weights 200, 300, 400, 600 and 700 in normal and italic, each with font-display: swap and a src list of woff2, woff and opentype on Adobe's origin. A <noscript> twin closes the block with a plain stylesheet link for a visitor whose onload never runs.
@font-face block as soon as it reaches it, with src pointing at Adobe's origin; the service stylesheet is demoted behind an onload swap and kept as the no-JavaScript fallback. The font files never leave Adobe's origin.On collection templates, and only there, the head emits an image preload. {% if request.page_type == 'collection' and collection.products.first.featured_image %} guards a <link rel="preload" as="image" fetchpriority="high"> whose href is the first product's featured image at 533 pixels wide, with an imagesrcset of five candidates at 165, 360, 533, 720 and 940 pixels and an imagesizes expression describing a four-column layout above 990 pixels, three columns above 769 and two below. Any other template, or a collection whose first product has no image, emits nothing.
Two smaller decisions sit in the same file. The theme's script bundles — vendor.js, theme.min.js and custom.min.js — are emitted through asset_url with defer; slider.js is enqueued only when a search featured collection setting is populated, and Shopify's shopify_common.js only when request.page_type contains customers/. And an IIFE in the body observes document.body with a MutationObserver — childList and subtree — and, on each mutation, stamps loading="lazy" and decoding="async" onto any img whose src includes cloudfront.net or klaviyo, marking each with data-optimized so a node is handled once. Images the theme renders itself keep whatever loading attributes their sections gave them.
Why This Way
Inlining the generated block puts the font file URLs in the document itself, rather than in a stylesheet on the service's origin whose only job is to say where the files are. Keeping the src URLs on use.typekit.net keeps the files where the service serves them. The service stylesheet isn't removed, because demoting it keeps two things: a <noscript> path, and a re-declaration of the same faces when the swap runs. font-display: swap is set on every one of the ten rules rather than left to the service's default; it's the descriptor that keeps text visible in a fallback face instead of hidden.
The image preload is conditioned because the right element differs per template. Emitted globally it would be a request for nothing on every non-collection page; the guard on request.page_type and the first product's featured_image limits it to the one case the layout can name.
Two costs come with this. The inline block is a copy of CSS that Adobe generates: change the kit in Adobe's dashboard and the copy in the layout is stale until someone re-pastes it. And the preload's ladder and imagesizes are hand-written in the layout, apart from the grid that renders the image; keeping the two aligned is a maintenance obligation nothing enforces. We haven't measured this storefront, and no timing claim is made here.
Implementation Notes
- The picked-face preloads are wrapped in
{% unless settings.type_base_font.system? %}and{% unless settings.type_heading_font.system? %}, each emitted withtype="font/woff2"andcrossorigin. - The Adobe block sits after the footer section group, not in
<head>; the head's only font tags are the two picked-face preloads. - The inline family is
span-compressed: weights 200, 300, 400, 600 and 700, normal and italic, ten rules in all, each withfont-display: swapand a three-formatsrclist on Adobe's origin. - The service stylesheet link is
rel="preload" as="style"withonload="this.onload=null;this.rel='stylesheet'"; the<noscript>twin is a plainrel="stylesheet"link to the same file. - The collection preload's
imagesizesis(min-width: 990px) calc((100vw - 130px) / 4), (min-width: 769px) calc((100vw - 120px) / 3), calc((100vw - 35px) / 2), and itshrefis the 533-pixel candidate. - Origin hints are cut to one
preconnectand twodns-prefetch; a longer list — analytics, reviews, chat, affiliate and email vendors — is retained in the file as a comment. slider.jsis enqueued only whensearch_featured_collection_1orsearch_featured_collection_2is set;shopify_common.jsonly oncustomers/page types.- The image observer marks each processed node with
data-optimizedand matches onlycloudfront.netandklaviyosources.
Edge Cases
- Empty or image-less collection: the guard on
collection.products.first.featured_imagefails and no preload is emitted. - JavaScript off: the
onloadswap never runs, and the<noscript>stylesheet link declares the licensed faces from Adobe's CSS instead. - System font chosen in theme settings: neither picked-face preload is emitted, because there's no
woff2to point at. - A font file that never arrives:
font-display: swapleaves the fallback face on screen rather than hiding the text, and swaps only if the file lands. - Klaviyo injects a form with images after load: the observer catches the new nodes, stamps them once and doesn't revisit them on later mutations.
- A theme-rendered image whose
srchappens to be on a matching host: it's stamped too — the observer tests the source, not the origin of the markup. - Customer account pages:
shopify_common.jsis requested there and nowhere else.
Platform Primitives Used
- Liquid section schema and settings — the layout reads
type_base_font.system?,type_heading_font.system?and the two search featured collection settings to decide which font preloads and which script to emit at all. - Responsive images — the collection preload's five-candidate
imagesrcset, built fromimage_urlat five widths, with animagesizesexpression for a<link>rather than an<img>. - Deferred CSS loading — the service stylesheet's
preload,onloadswap and<noscript>twin.
Integrations in Play
- Klaviyo — coexists with the layout; the image observer stamps loading attributes onto images its forms inject after load, and leaves everything else alone.
- Adobe Fonts — the licensed typeface's service. Its generated
@font-faceblock is inlined in the layout and its stylesheet demoted; the font files stay on its origin.
Where It Runs
Everywhere the layout does. theme.liquid wraps every template on the storefront, so the font preloads, the Adobe block, the script deferrals and the image observer apply to every page view. The image preload is the exception: it renders on collection templates only, and only when the first product has a featured image.
What This Demonstrates
- Media and asset loading strategy — the primary capability, in its layout-level form: font delivery decided in the document rather than left to the service's default, one image preload placed under an explicit template condition, and app-injected images given attributes the app didn't set.
How We Know
The layout file, roughly 150 lines of which are this build, read from the live theme. Documented from the code; no client-side record of the decision survives. The placement of the Adobe block, the guards on the font preloads and the condition on the image preload are all read directly from the markup; the reasoning is reconstructed from what the code does, and this page says so rather than dressing it up.
Related Builds
- Layout shell rewritten as a load-order contract: origin hints, two stylesheet tiers and an interaction-gated session recorder — Nudestix's layout doing the same job on Dawn, where the emphasis lands on stylesheet tiers and a gated vendor script rather than fonts.
- Server-rendered page-context dataLayer, declared in the head and pushed at browser idle — the same storefront's other layout-level work.
- DE Hero: art-directed image/video hero with layout controls — LUS Brands, where the image-loading decisions are pushed into the section rather than made once in the layout.
The Buy-vs-Build Question
A speed app applies generic rules from outside the theme — deferring, hinting, lazy-loading whatever it finds — and it can't know that a font service's files have to stay on that service's origin, or that one template type has a nameable first image and the others don't. Writing those decisions into the layout bought a head you can read and a font block you can diff, and cost a hand-maintained copy of generated CSS. The wider trade is argued at site speed optimization apps: buy or build?.
Provenance & Evidence
- Client: Three Ships — threeshipsbeauty.ca
- Surfaces: Global — the layout wraps every template; Collection — the image preload renders there only
- Templates served: all of them; the image preload on collection templates alone
- Complexity: Medium
- Scale: roughly 150 lines inside the layout file
- Attribution: Deploi-authored. The inlined Adobe Fonts block and its demoted loader, the collection image preload, the trimmed origin hints and the image observer are ours. They sit inside the layout of Palo Alto 5.8.0 by Presidio Creative, a paid premium theme the brand licenses; the theme's own font-picker handling and asset enqueues remain the vendor's.
- Status: Live, verified 2026-09-07
- Evidence: one layout file read from the theme
- Confidence: Moderate — documented from the code; no client-side record of the decision survives
- Not claimed: any timing, score, before-and-after or qualitative speed statement about this storefront
- Primary capability: Media and asset loading strategy
Ready to Know Exactly Where Your Fonts Come From?
If your brand typeface arrives through a stylesheet nobody on your team has read, and your preloads were set once by a theme you didn't write, the answers are in your layout. Contact us today and we'll go through it with you, tag by tag — and if you want a number out of it, we'll talk about measuring first.