Examples
EN

Incremental Build

Incremental build tracks file checksums and dependency changes to skip unchanged pages, significantly reducing rebuild times for large sites.

hwaro build --cache

When to Use

Incremental builds are most effective when:

Usage

Enable caching

hwaro build --cache

On the first run, Hwaro builds every page and saves checksums to .hwaro_cache.json. On subsequent builds, only changed files are re-processed.

Force a full rebuild

hwaro build --cache --full

The --full flag clears the cache and rebuilds every page from scratch. The new cache is saved afterward, so the next build without --full will be incremental again.

How It Works

Change detection

Each cached entry tracks:

When mtime hasn't changed, the file is considered unchanged without reading its content. When mtime differs, Hwaro computes and compares the content hash to confirm the change is real.

Dependency invalidation

Beyond per-file checksums, Hwaro tracks what each page actually depends on:

Template dependency tracking requires every template reference to be a string literal. If any template uses a dynamic reference ({% include some_var %}), Hwaro falls back to whole-site invalidation: any template change rebuilds every page. You can also opt out explicitly:

[build]
template_deps = false  # any template change rebuilds every page

What gets skipped

When a page is unchanged and its dependencies haven't changed:

Serve mode

The development server (hwaro serve) uses a more targeted incremental strategy:

Change Type Strategy
Content files only Re-parse and re-render only affected pages + neighbors, plus any listing page the edit changed (see below)
Template files only Re-render only pages whose template closure includes an edited template (all pages when tracking is off, the graph has dynamic references, or the edited file is under templates/hooks/); a full rebuild when the edited file is a [[content.generate]] body_template or a partial it includes
Config file Full rebuild
Static files only Copy only changed files; a full rebuild when a file lands where the build writes a page or a generated file (robots.txt, a feed, a taxonomy page, an alias stub, …), feeds a cache-busting ?v= hash, or is read by a literal load_data() path in a template

A listing page is one whose template closure renders a global set — the homepage's "latest posts" loop over site.pages, an archive, a paginated index, a nav built from site.menus, a tag pill resolved through get_taxonomy_url(), a footer printing get_page(path="about.md").title, a shortcode the page's content calls to loop over a section's pages. It owns none of the page you edited, so the selections above never reach it. Serve therefore fingerprints those sets either side of the rebuild and re-renders the listing pages whose set moved.

Each kind of listing is gated on a fingerprint of its own inputs, not on one site-wide digest. That matters because a nav partial in the shared base layout is in every page's template closure: a coarser gate would answer "re-render the whole site" for any metadata edit. A menu refreshes when a menu entry's title or URL moves, a tag pill when the term set changes, a site.pages loop when the page set changes — and an edit that moves nothing a listing prints (a body-only change on a site whose listings show no excerpts) still re-renders just the edited page.

Serve also watches an [assets] source_dir that lives outside static/, so bundle sources kept in their own directory rebuild on save like any other asset.

Removed pages

A cold build starts from an empty output directory, so a page you delete is gone from public/ on the next build. --cache keeps the directory — that is the point — so removals are handled from the cache instead: every entry records the file the page wrote, and on each build the entries no live page claims have their output deleted.

That covers a page that was deleted, renamed, moved to a new URL (slug, path, a permalink rule), turned into a draft, passed its expires date, or set render = false.

Everything a removed page brought with it goes too: its aliases redirect stubs, its AMP mirror, its auto-generated OG image, the taxonomy term page of a tag nobody uses any more (with that term's feed), and the pagination page a section no longer fills. Turning a whole feature off — [amp] enabled = false, [og.auto_image] enabled = false, [image_processing] enabled = false, or the --skip-og-image / --skip-image-processing flags — removes what it used to publish on the next build for the same reason, as does dropping a width from [image_processing] widths.

Files that have a source but no cache entry are covered the same way, by recording what each build publishes and deleting what the next one no longer does: static/ copies, [content.files] and raw (.json/.xml) copies, page-bundle assets, and the fingerprinted asset bundles — so editing a stylesheet replaces main.<hash>.css instead of leaving every past revision in public/assets/.

All of this relies on the cache remembering what it wrote. When it has no usable record of the output directory — no .hwaro_cache.json yet (the tree came from a plain hwaro build), a corrupt one, or one last used for a different -o — a --cache build clears the output directory first, exactly like a cold build (and under the same ownership rules), then records it. The cache remembers one output directory at a time, so alternating hwaro build --cache and hwaro build --cache -o dist rebuilds each tree from scratch on every switch. hwaro serve --cache keeps a separate cache (see below), so a serve session never costs the next hwaro build --cache its record of public/.

A --cache build's output is therefore byte-identical to a clean build's, and hwaro build --full is not needed to clear anything. (--full only clears the cache, so it does nothing at all without --cache; a plain hwaro build already rebuilds everything.)

Cache File

The cache is stored in .hwaro_cache.json at the project root (hwaro serve --cache uses .hwaro/serve_cache.json, next to the .hwaro/serve/ tree it describes; .hwaro/ keeps itself out of version control). This file contains:

Add .hwaro_cache.json to your .gitignore (the one hwaro init scaffolds already lists it):

.hwaro_cache.json

Flag Reference

Flag Description
--cache Enable incremental build caching
--full Force a complete rebuild (clears and repopulates the cache)

Examples

# First build — creates cache
hwaro build --cache

# Edit a few files, then rebuild — only changed pages are re-rendered
hwaro build --cache

# Something seems off — force a clean rebuild
hwaro build --cache --full

# Combine with other flags
hwaro build --cache --minify

See Also