Guide 03 · Developer docs

The theme repo and the asset pipeline

Everything a developer changes on a Core dna site lives in its theme repository. This guide covers the repository and its build, printing records in templates, stylesheets and client-side code, and the parts of a site around the main content.

L1–L2
Unit 03 · Stylesheets and SCSS

Critical and deferred CSS

The main stylesheet should block rendering; styles the first screen does not need should not. What to inline in the head, how to load a stylesheet late under the site's Content Security Policy, attaching the handler from your bundle instead of inline, and the measurements that decide whether a change was worth it.

Who this is for. The developer working on how fast a page first appears. Afterwards you can move a stylesheet out of the critical path in a way that works under the site's policy, and prove with numbers that it helped.

What should block, and what should not

A stylesheet linked in the <head> blocks rendering until it has loaded. That is right for the main stylesheet, without which the first screen renders unstyled and then jumps. It is wasted for styles of things the visitor has not reached yet: a lightbox, a slider further down, a phone-number widget in a form, the third-party widgets of a vendor stylesheet.

Inline only what prevents a jump

Put in the <head>, inline, only the rules whose absence causes a visible shift before the stylesheets arrive: the @font-face rules of the fonts above the fold, with font-display: swap, and a rule hiding elements that JavaScript will reveal. One production base template does exactly that, in a <style> block before its stylesheet links. A <style> block needs no nonce under the default policy, which restricts scripts only.

None of the production repositories extracts "critical CSS" with a tool, and most do not need to: a lean main stylesheet, around 30 KB gzipped, is fast enough to block.

Loading a stylesheet late

The pattern most guides show is a preload that turns itself into a stylesheet:

HTML A preload with an inline handler
<!-- the onload attribute is inline script, and needs a nonce it cannot carry -->
<link rel="preload" href="/dist/vendor.css" as="style" onload="this.rel='stylesheet'">

An inline event handler such as onload cannot carry a nonce, so attach the handler from a script in your bundle instead, with addEventListener, as one production site does for its vendor stylesheet, and keep a <noscript> link for visitors without JavaScript:

Template code
<link rel="preload" href="/dist/vendor.css?v={$_currentSite->getLastVcsHash()}" as="style">
<noscript><link rel="stylesheet" href="/dist/vendor.css?v={$_currentSite->getLastVcsHash()}"></noscript>
<script nonce="{$_inlineScriptNonce}">
  window.assetVersion = {$_currentSite->getLastVcsHash()|json_encode};
</script>
<link rel="preload" href="/dist/vendor.css?v={{ _currentSite.getLastVcsHash() }}" as="style">
<noscript><link rel="stylesheet" href="/dist/vendor.css?v={{ _currentSite.getLastVcsHash() }}"></noscript>
<script nonce="{{ _inlineScriptNonce }}">
  window.assetVersion = {{ _currentSite.getLastVcsHash()|json_encode }};
</script>
JavaScript src/js/utils/load-stylesheet.js
export function loadStylesheet(href) {
  const link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = href;
  link.media = 'print';               // fetched without blocking rendering
  link.addEventListener('load', () => { link.media = 'all'; });
  document.head.appendChild(link);
}

loadStylesheet(`/dist/vendor.css?v=${window.assetVersion}`);

The preload starts the download early; the script applies the file once it has arrived. The handler is attached with addEventListener in the bundle, which the policy allows. The URL carries the same cache-buster as every other asset: Cache-busting asset URLs.

Decide by measurement

  1. Run Lighthouse, or WebPageTest on a throttled mobile profile, on each page type before the change: home, a content page, a listing, the checkout. Record First Contentful Paint, Largest Contentful Paint and Cumulative Layout Shift.
  2. Open the Coverage panel in Chrome's developer tools and reload: it shows how much of each stylesheet the first screen used. A stylesheet mostly unused on first load is a candidate for deferring.
  3. Make the change, and measure again on the same pages and profile.
  4. Keep it only if the paint times improve and layout shift does not grow. A deferred stylesheet that restyles something already visible shows up as shift.
Go deeper

Was this page helpful?

Your answer goes to the docs team.