Static assets
Authoring static assets (photographs, svg, fonts) at the template level or in a section folder, the derived vs passthrough import lanes, and how assets differ from content images.
A static asset is a file that is part of the template itself — a logo, a background texture, an icon, a brand webfont. It is authored once, is the same for every deliverable rendered from the template, and is referenced either from your CSS or from a component.
Where an asset lives
Section titled “Where an asset lives”An asset lives in one of two places:
- Template level — e.g.
templates/<template>/assets/— shared across every section in the template. Reference it from a section’s CSS with a../../assets/…path (below). - Section folder — e.g.
sections/<section>/assets/— local to that one section. A section-folder asset must be referenced: imported inRenderer.tsxorurl()-referenced from one of the section’s CSS files. An unreferenced binary in a section folder is flagged bybundle-binary-asset— it has no build meaning and can never be served.
Keep a logo, texture, or font that several sections share in the template-level folder; keep one that only a single section uses alongside that section.
Supported files
Section titled “Supported files”.png .jpg .jpeg .webp .avif photographs — derived (see below).svg .gif .ico vector / animated images.woff .woff2 .ttf .otf .eot fonts.mp4 .webm .mov .mp3 .wav audio / video.pdf .zip documentsReferencing an asset — from CSS with url()
Section titled “Referencing an asset — from CSS with url()”Reference a template-level asset from a section’s styles.css with url() —
a @font-face src or a background-image resolves to the asset’s built URL,
identical in the template-kit dev preview, in the editor, and on the published
page. Hand-CSS opens with a css-reason: comment (see
css-reason):
/* css-reason: brand webfont + crest watermark, both template assets */@font-face { font-family: "Brand Serif"; src: url("../../assets/fonts/brand.woff2") format("woff2"); font-display: swap;}
.hero-crest { background-image: url("../../assets/crest.svg");}The ../../assets/… path climbs out of the section folder to the template-level
assets/ directory. A font you declare here is available to the whole section.
If the family you want is one the template’s theme already loads, reference that
instead — see TokenTheme; a font the page already has
costs nothing to reuse. A font the whole template wants belongs in a theme fonts
entry in the first place, where its src resolves against theme.ts — see
Loading a web font.
A section-folder asset is referenced the same way, with a section-relative path instead — no climbing out of the folder:
/* css-reason: hero watermark, local to this section */.hero-crest { background-image: url("./assets/crest.svg");}Referencing an asset — from a component with import
Section titled “Referencing an asset — from a component with import”An asset you render as an element is imported in the component. What the import gives you depends on the file, following the same split Next.js draws:
| Import | You get | Render with |
|---|---|---|
.png .jpg .jpeg .webp .avif |
an AuthorImage object |
<Image value={…} alt="…"> |
.svg .gif .ico, fonts, video, documents |
the built URL string | <img src={…}>, url(), … |
The kit ships an ambient TypeScript declaration for both lanes, so either import
typechecks with no per-workspace setup. (Hovering a raster import shows the shim’s own
TemplateAuthorImage; it is the same shape as the exported AuthorImage, which is the
name to write in your own annotations.)
A photograph — import + <Image>
Section titled “A photograph — import + <Image>”A raster image you import is derived at build time into a full responsive ladder —
several widths in several modern formats, the same treatment the platform gives a
property’s own uploaded photo. The import gives you an AuthorImage describing that
ladder, which you hand straight to the
Image primitive:
import { Image } from "@homepages/template-kit";
import rooftop from "./assets/rooftop.jpg";
export default function Renderer() { return ( <div className="aspect-[3/2]"> <Image value={rooftop} alt="Rooftop deck at sunset" frame={false} fit="cover" /> </div> );}This is the bare Image primitive, not Slot.Image — a template asset has no slot to
bind to. frame={false} renders a single <img class="w-full"> and lets the parent
establish the box; pass a
FrameConfig instead if you want
the primitive to own the aspect box.
alt is required here, and the compiler enforces it: an AuthorImage has no alt of
its own to fall back on. Pass alt="" for a purely decorative image — a deliberate empty
string, not an omission.
Commit one honest master per image, at full quality. The master itself is never served, so its file size costs you nothing at the edge; every width a visitor actually downloads is derived from it. Do not hand-export a set of sizes — that is the build’s job.
Where a build has no image toolchain available (a bare local preview), the import still
gives you an AuthorImage, just with no ladder in it, and <Image> renders a single
<img>. Your code is identical either way — the preview is simply unoptimized.
A logo, icon, or font — import + ordinary markup
Section titled “A logo, icon, or font — import + ordinary markup”Vector and animated files are passed through untouched: rasterizing line art would be a
regression, and a ladder cannot express an animation. These resolve to the asset’s built
/section-assets/… URL — the same URL in the dev preview, the editor, and the
published page:
import crest from "./assets/crest.svg";
export default function Renderer() { return <img src={crest} alt="" className="h-8 w-auto" />;}A plain <img> is right here: there is one file, at one size, with no ladder to choose
from.
Which to use
Section titled “Which to use”- CSS decoration (backgrounds,
@font-face, masks) →url(). - A photograph rendered as an element →
import+<Image value={…} alt="…">. - A logo, icon, or font rendered as an element →
import+<img>/url(). - A tiny glyph or icon → inline
<svg>markup — no file at all.
Assets vs. content images
Section titled “Assets vs. content images”These are two different things; keep them apart.
- A static asset is authored template decoration — a logo, a texture, a brand
font. It ships inside the template, is the same on every deliverable, and you
reference it from your CSS with
url()or by importing it into a component (above). - A content image is the property’s own content — listing photos, floor
plans, a headshot. It differs on every deliverable and flows through the
section’s slots as an
ImageValue, never as a file in your template. Declare an image slot in your schema and render it withSlot.Image; the platform supplies the actual photo per deliverable.
The same line runs through the non-image files. A .pdf above is a template document —
a spec sheet the agency ships with the design, identical on every deliverable. A
property’s own brochure, disclosure pack or fee schedule is content: it is uploaded per
listing, so it belongs in a document slot and
renders through
Slot.Document, never as a
file committed into a section folder.
Both end up in the same responsive-image machinery — the distinction is where the bytes come from, not how well they are served. A photograph you ship with the template gets the same width-and-format ladder a user’s upload does.
Rule of thumb: if the picture is the same for every home the template ever
renders, it’s an asset — place it at the template level or in the section folder
and reference it from CSS or a component import. If it changes with the
property, it’s content — give it a slot. Never hard-code a listing photo as an
asset.
Derived images are cached
Section titled “Derived images are cached”Deriving a ladder is expensive — a single full-size photograph is two dozen separate
encodes — so the results are cached inside your workspace, under .tk-dev-cache/media/.
A second template-kit check over unchanged photographs re-uses them instead of encoding
again; editing one photograph re-derives that photograph only.
Entries are addressed by the contents of the master, so a stale one cannot exist: change
the file and you get a different entry. The directory is safe to delete at any time —
the only cost is deriving again. It is already ignored by the workspace’s .gitignore,
and never commit it.
It bounds itself to 512 MB, discarding whichever images you used least recently. The
preview server (template-kit dev) shares the same cache, so a restart does not
re-encode either.
Keep them lean
Section titled “Keep them lean”Nothing refuses a section for the assets it carries — a published site ships only the
assets its rendered pages reference. But an asset over 150 KB is reported by name, since
past that it is usually an accident: see size-assets. Subset a
webfont to the weights and characters you use, and drop anything a section doesn’t
actually reference.
Photographs are the exception: do not pre-shrink a raster you import, because the build derives every served width from your master and the master itself is never downloaded by a visitor. Shrinking it only lowers the ceiling on what a large screen can be given. Everything else ships as the bytes you committed, so those are worth trimming.
template-kit check reports which of a template’s photographs derived a
ladder and at what cost, and which merely passed through unoptimized (no image toolchain
in this install) — run it if a preview looks soft or the asset budget runs over.
See also
Section titled “See also”- Slot primitives —
Imageand the rest, including the frame options a template asset can be given. TokenTheme— the fonts and tokens a template already loads, before you ship another file.bundle-binary-asset— why an unreferenced binary in a section folder is reported.size-assets— the per-file guideline a heavy asset is reported against, and why nothing is refused.- Ship a static asset — the same ground as a task walkthrough.