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 01 · Anatomy of a theme repo

Cache-busting asset URLs

Built files are sent with a one-year cache lifetime, so a changed file needs a changed URL. The deploy-hash query parameter in both engines, why it is empty on some environments and what to do then, assets that scripts load themselves, and what returning visitors see without it.

Who this is for. Anyone who links a stylesheet, a script, a sprite or a font from a template. Afterwards every asset URL you write changes when its file can have changed, and you know why a deploy sometimes appears not to have happened.

Why the URL has to change

On a production environment the platform serves files from the repository with Cache-Control: public and a max-age of one year. A browser that has /dist/app.css will not ask for it again for a year, whatever you deploy. Change the URL whenever the file can have changed, so a returning visitor's browser treats it as new and fetches the current file along with the current markup.

The deploy hash

_currentSite gives the hash of the last deployed commit for the current environment. Append it to every asset URL as a query parameter:

Template code
<link rel="stylesheet" href="/dist/app.css?v={$_currentSite->getLastVcsHash()}">
<script src="/dist/app.js?v={$_currentSite->getLastVcsHash()}" nonce="{$_inlineScriptNonce}" defer></script>
<link rel="stylesheet" href="/dist/app.css?v={{ _currentSite.getLastVcsHash() }}">
<script src="/dist/app.js?v={{ _currentSite.getLastVcsHash() }}" nonce="{{ _inlineScriptNonce }}" defer></script>

The value is the first seven characters of the commit hash recorded by the environment's last deploy, so it changes with every deploy and stays the same between them. Browsers keep the file for a year and fetch it again exactly once after each deploy. _currentSite.lastVcsHash also works in Twig; the explicit call reads the same in both engines.

Every asset tag needs it, including stylesheets loaded late, the favicon if it can change, and a sprite referenced with <use href>. A missing one is easy to spot in the base template: search for /dist/ (or /static/) and check each match.

When the hash is empty

getLastVcsHash() returns an empty string on an environment with no recorded deploy: one whose files are uploaded by SFTP or an editor's automatic upload rather than deployed from a branch. Use one of these instead, so the URL still changes with every upload:

  • getAnySiteLastVcsHash() returns the current environment's hash when it has one, and otherwise the latest hash recorded by any environment with the same name. A codebase shared by several sites, where only one site's environment records deploys, uses it so that all of them get a hash.
  • A build constant that you change whenever you upload new output, written next to the hash: ?v={$_currentSite->getLastVcsHash()}{$assetBuild}. This documentation site does that for its upload-based development environment.

Files the CSS and JavaScript refer to

A query parameter on app.css does not reach the fonts and images that app.css refers to with url(), or the chunks a bundle loads. Let the build give those files content-hashed names, so a changed file gets a new name:

JavaScript webpack config (extract)
output: {
  chunkFilename: 'chunks/[name].[contenthash:8].js',
  assetModuleFilename: 'static/[hash][ext][query]'
}

A file that a script fetches by a URL it builds itself, a stylesheet added from JavaScript or an SVG sprite, needs the hash passed to it. Print it once into the page and read it from the script:

Template code
<script nonce="{$_inlineScriptNonce}">
  window.assetVersion = {$_currentSite->getLastVcsHash()|json_encode};
</script>
<script nonce="{{ _inlineScriptNonce }}">
  window.assetVersion = {{ _currentSite.getLastVcsHash()|json_encode }};
</script>

Do not fall back to a timestamp such as Date.now() when the value is missing: it makes every page view a fresh download and defeats the cache altogether.

A deploy that seems not to have happened

When one person sees a change and another does not, compare the ?v= values in the two page sources. The same value on both, with different files, means the tag has no hash or the hash is empty on that environment.

Go deeper

Was this page helpful?

Your answer goes to the docs team.