Gohyde’s asset pipeline (internal/assets) walks the
configured source directories, processes each file (Liquid, Sass, fingerprint),
writes the result to the output directory, and records a manifest.json so
Liquid tags and filters resolve to the final URL.
The pipeline runs before the page render pass, so asset_path / asset_url
and the {% asset %} family always see the current digest in the same build.
assets:
digest: false # content-hash fingerprinting (true for production)
destination: assets # output subdir under the site root
sources: # directories scanned, in order
- _assets
- _assets/css
- _assets/js
- _assets/images
- assets
cdn:
url: "https://cdn.example.com" # absolute CDN base; overrides baseurl
Asset URLs include the site baseurl so sub-path deploys resolve correctly:
baseurl: "/routeway"
{{ "logo.svg" | asset_path }} → /routeway/assets/logo.svg
{% asset "app.css" @path %} → /routeway/assets/app.css
Precedence:
assets.cdn.url is set, the URL is <cdn.url>/assets/<file> (baseurl is
not applied — the CDN URL is already absolute).<baseurl>/<destination>/<file>.Asset URLs carry only the baseurl, never site.url. For an absolute URL,
prepend {{ site.url }} in the template:
<meta property="og:image"
content="{{ site.url }}{% asset {{ site.mainlogo }} @path %}" />
<!-- → https://example.com/routeway/assets/logo.svg -->
digest (default true) appends a content hash to asset filenames
(style.css → style-a1b2c3d4.css) for cache-busting. It is forced off
under gohyde serve regardless of config, so dev URLs stay stable across live
reloads — no churn while you edit. gohyde build fingerprints as configured.
Treat digests as a production concern.
{{ "css/style.css" | asset_path }} → /assets/css/style-a1b2c3d4.css (when digest on)
{{ "css/style.css" | asset_url }} → same (alias)
{{ "css/style.css" | asset_digest }} → a1b2c3d4
The manifest is written to <destination>/assets/manifest.json.
Four tags emit the right HTML for the asset type:
{% asset "app.css" %}
→ <link rel="stylesheet" href="/assets/app-a1b2c3d4.css">
{% asset "app.js" defer %}
→ <script src="/assets/app-a1b2c3d4.js" defer></script>
{% asset "logo.png" alt="Logo" class="brand" %}
→ <img src="/assets/logo-a1b2c3d4.png" width="200" height="80" alt="Logo" class="brand">
{% javascript "main" async %} → <script src="/assets/js/main-….js" async></script>
{% stylesheet "main" media="print" %} → <link rel="stylesheet" href="/assets/css/main-….css" media="print">
{% image "hero.jpg" alt="Hero" %} → <img src="/assets/images/hero-….jpg" …>
Append any of @uri, @src, @href, @path, @url to get just the resolved
URL instead of a full HTML tag:
<meta property="og:image" content="{{ site.url }}{% asset site.mainlogo @path %}" />
<div style="background:url({% asset 'bg.svg' @url %})"></div>
The first argument can be quoted, a variable, or a {{ … }} expression:
{% asset "logo.svg" %} ← string literal
{% asset item.cover %} ← resolved against the loop/page variable
{% asset {{ site.mainlogo }} @path %} ← config value via {{ … }}
A missing asset logs a warning to stderr and renders an empty string. It
never injects an HTML comment like <!-- asset error --> into the output, and it
never hard-fails the build:
Warning: asset "missing.svg" not found
The processor is keyed by the file’s source extension:
| Source file | Output | Processing |
|---|---|---|
main.js.liquid |
main.js |
Liquid-rendered (with site in scope), .liquid stripped |
style.scss |
style.css |
Compiled by Dart Sass |
style.scss.liquid |
style.css |
Liquid first, then Sass |
app.css |
app.css |
Copied (fingerprinted if digest: true) |
Unprocessed files that aren’t fingerprinted (images, video, fonts on a site
without digest) are streamed, not loaded into memory, and skipped when the
output already has the same size and is at least as new as the source.
Processed or fingerprinted outputs are skipped when their bytes are unchanged.
Either way the kept file’s modification time is refreshed.
Files prefixed with _ (Sass partials) are never emitted standalone.
CMS media library: the CMS serves its image library from the first
assets.sourcesentry ending inimages(e.g._assets/images), falling back toassets/images. Picking an image from a pipeline source inserts the bare asset key (for{% asset %}), not an absolute path.
Stale output: a full build deletes destination files it didn’t produce (keep extra files with
keep_files:), so leftovers such as raw*.liquid/*.scssfrom an older build disappear on their own; with--incremental, clean the destination yourself.
Gohyde compiles .scss/.sass via Dart Sass when a real sass/dart-sass
binary is on PATH. It verifies the binary with --version and rejects the
legacy Ruby sass gem (which can’t accept --no-source-map). With no real
compiler available, sources pass through unchanged and a warning is printed —
the build still succeeds.
sass:
sass_dir: _sass
style: expanded # default, as in jekyll-sass-converter; or compressed
tailwind:
enabled: true
input: assets/css/input.css
output: assets/css/tailwind.css
bundles: # optional: extra css compilations
- input: _assets/css/admin.css
output: assets/admin.min.css
Each bundles entry gets its own CLI run — and its own --watch process under
gohyde serve. A failing bundle doesn’t stop the others.
gohyde serve starts tailwindcss --watch only when enabled: true or the
input file exists — so sites that disable Tailwind don’t get spammed with
input.css does not exist.
gohyde build runs Tailwind while the pages render, since Tailwind scans
source templates. If the input CSS (or a tailwind.config.*) mentions the
destination directory — e.g. a v3 content: ["./_site/**/*.html"] — Tailwind
runs after the pages are written instead.