Assets (images & documents)
Images and documents live co-located in the page bundle as bare filenames —
content/<slug>/<asset> (an image’s asset is hero.jpg, never a path). A site
build has two jobs: turn that bare name into a URL, and get the file to where it’s
served. The split that keeps this generic: the engine owns the source (assets
are in content/<slug>/), the SSG owns the destination (public/, a CDN).
Resolve a URL — assetUrl
One resolver, used identically for a card thumbnail and a page-body image, so an asset resolves the same way everywhere:
import { assetUrl } from "@your-project/content-types";
assetUrl("/products/alfa-laval-sru", "image.jpg");
// → "/content/products/alfa-laval-sru/image.jpg"
// a projection card thumbnail — the card already carries slug + asset:
assetUrl(card.slug, card.thumbnail.asset);It’s ${ASSET_BASE}${slug}/${asset}. ASSET_BASE comes from
site.assets.base (default /content, matching a public/content/<slug>/<asset>
served layout). Pass a third arg to override — e.g. an absolute CDN origin:
assetUrl(card.slug, card.thumbnail.asset, "https://cdn.example.com/content");Everything projects the bare asset (image.jpg) — cards and bundle bodies
alike — and the SSG resolves with assetUrl. Nothing pre-bakes a URL, so an asset
never resolves two different ways depending on where it’s rendered.
Deliver the files — copyMeshAssets
The engine knows where every asset is; your build copies them to the served dir.
copyMeshAssets mirrors the tree; you pass the destination so it stays in lockstep
with ASSET_BASE:
import { copyMeshAssets, meshAssets } from "@your-project/content-types";
// in your build step — mirror content/<slug>/<asset> → public/content/<slug>/<asset>:
copyMeshAssets("public/content", { status: renderableStatuses(env) });With ASSET_BASE = /content and public/ as the served root, that lands each
file exactly where assetUrl points. Prefer to run your own copy loop? Take the
inventory instead:
| Function | What it does |
|---|---|
assetUrl(slug, asset, base?) | Resolve a bare asset → served URL (${base}${slug}/${asset}). |
pageAssets(slug, opts?) | One page’s co-located assets — { slug, name, path }[] (excludes index.yaml/dotfiles). |
meshAssets(opts?) | Every renderable page’s assets (status-filterable) — the copy-step inventory. |
copyMeshAssets(dest, opts?) | Mirror content/<slug>/<asset> → dest/<slug>/<asset>; returns the manifest. |
Point dest at the directory your ASSET_BASE maps to under the served root
(ASSET_BASE /content → dest: "public/content"). The engine mirrors the
<slug>/<asset> layout; keeping dest aligned with ASSET_BASE is what makes
the copied file and the resolved URL agree.
Related
- Projections (cards & lists) — cards carry a bare
thumbnail.asset; resolve it withassetUrl - Publish status & environments — ship only the renderable set’s assets
- Content-types package — the full API