Astro 5’s built-in glob() content loader computes entry digests exclusively from raw Markdown text, leaving co-located vector schematics and raster cover images untracked in the build cache [MMJ18]. We engineered assetAwareGlob to fold sorted sibling asset hashes into each entry’s Merkle digest and prune build-time inlined SVGs from redundant image optimization [Mer88].
| Architectural Dimension | Built-In glob() Loader | Ad-Hoc Dev Cache-Wipe Workaround | Composite Merkle assetAwareGlob (SYSTEM-012) |
|---|---|---|---|
| Digest Input Scope | Raw .md/.mdx string only | Raw .md/.mdx string only | Markdown body + co-located .svg/raster hashes + pipeline plugins [Mer88] |
In-Place Asset Edit (hero.jpg / .svg) | Misses mutation; serves stale .astro/data-store.json | Deletes entire .astro/data-store.json + server.restart() | Re-renders only the affected entry in < 200ms [MMJ18] |
| Vite Watcher Integration | Watches .md/.mdx globs only | External Chokidar hooks in astro.config.ts | Bound Proxy on context.watcher + 100ms debounced asset mapping |
| Build-Time Inlined SVGs | Processed redundantly by astro:assets | Left in localImagePaths | Pruned from vfile.data.astro.localImagePaths in rehype-inline-svg.ts |
Why Does Astro’s Built-In Glob Loader Miss Co-Located Asset Mutations?
Astro’s default glob() loader misses co-located asset mutations because it evaluates generateDigest strictly over the Markdown string and frontmatter metadata [MMJ18]. When an author edits a sibling .svg diagram or overwrites hero.jpg in place, the entry digest in .astro/data-store.json remains unchanged and serves stale build artifacts until a manual cache wipe.
In the formal taxonomy of build systems established by Mokhov, Mitchell, and Peyton Jones [MMJ18], Astro 5’s Content Layer acts as a constructive verifying trace store backed by .astro/data-store.json. A constructive trace cache is only sound when its verification key captures the complete transitive closure of inputs consumed during compilation [DJV04]. As documented in our architectural specification SYSTEM-012, two compilation paths violated this invariant: first, rehype-inline-svg.ts reads co-located .svg files synchronously via fs.readFileSync outside Vite’s module graph; second, replacing hero.jpg or preview.jpg with an identical filename leaves the frontmatter path string ./hero.jpg untouched.
Previously, astro.config.ts worked around these stale cache hits by watching .svg files and deleting the entire .astro/data-store.json cache or triggering a full server.restart(), which invalidated every collection simultaneously and added multi-second rebuild latency.
How Does Our Custom Content Loader Compute Combined Merkle Asset Digests?
Our custom assetAwareGlob wrapper intercepts generateDigest to combine the base Markdown digest with a sorted cryptographic fingerprint of all co-located directory assets and pipeline plugins [Mer88] [DJV04]. Simultaneously, it proxies Vite’s file watcher with bound this context to map sibling asset edits directly back to the affected Markdown entry for sub-second updates.
Wired across all content collections in src/content.config.ts, computeEntryAssetFingerprint scans the entry directory (plus parent presentation directories for slides/ and explicit ./ or ../ references) for .svg, .png, .jpg, .webp, .avif, and .gif files, alongside global pipeline dependencies including src/styles/graphics.css. Using getFileContentHash from src/commons/server/content-hash.ts—which caches 16-character MD5 digests keyed by mtimeMs and byte size—the loader constructs a deterministic Merkle-style fingerprint [Mer88]:
To trigger surgical single-entry rebuilds during astro dev without mutating Markdown file timestamps (fs.utimesSync), src/commons/server/content-loader.ts wraps context.watcher in an ES Proxy that captures Astro’s internal globChangeHandler while binding native Vite FSWatcher methods (Reflect.get(target, prop, target).bind(target)):
const wrappedWatcher: FSWatcher | undefined = context.watcher
? new Proxy(context.watcher, {
get(target, prop) {
if (prop === 'on') {
return (event: string, listener: (changedPath: string) => void) => {
if (event === 'change' && !globChangeHandler) globChangeHandler = listener;
return target.on(event, listener);
};
}
const value = Reflect.get(target, prop, target);
return typeof value === 'function' ? value.bind(target) : value;
},
})
: undefined;
const enhancedContext: LoaderContext = {
...context,
watcher: wrappedWatcher,
generateDigest: (data) => {
const baseDigest = String(originalGenerateDigest(data));
const mdFilePath = typeof data === 'string' ? mdFileByBaseDigest.get(baseDigest) : undefined;
const assetFp = mdFilePath ? computeEntryAssetFingerprint(mdFilePath, data as string) : '';
return assetFp ? String(originalGenerateDigest(`${baseDigest}:${assetFp}`)) : baseDigest;
},
};When a co-located .svg or raster image changes on disk, findAffectedMarkdownFiles resolves the owning .md/.mdx entry within a 100ms debounce window, refreshes mdFileByBaseDigest, and invokes globChangeHandler(mdFile) so Astro re-renders only that entry in under 200ms.
Why Must Build-Time Inlined SVGs Be Pruned From Astro’s Image Pipeline?
Build-time inlined SVGs must be pruned from vfile.data.astro.localImagePaths because Astro’s Remark pass registers every Markdown image syntax node for asset optimization before Rehype plugins execute. Filtering out inlined .svg paths in rehype-inline-svg.ts prevents redundant astro:assets processing while keeping theme-adaptive vector diagrams inside the document DOM.
Because our diagrams rely on CSS custom properties (var(--svg-*) from src/styles/graphics.css) to switch seamlessly between Soft Nordic Pastel light mode and dark mode, src/plugins/rehype-inline-svg.ts replaces <p><img src="./diagram.svg"></p> with an inline <figure class="svg-diagram"> tree. During this transformation, the plugin computes an 8-character SHA-256 scope prefix (svg-${hash}-) over both the normalized path and SVG content (data-svg-hash) so internal <marker> and <clipPath> IDs never collide across multiple diagrams on a page.
However, Astro’s internal Remark image plugin runs before Rehype and pushes every  path into vfile.data.astro.localImagePaths. By filtering inlinedSrcs and .svg entries out of astroData.localImagePaths at the end of rehype-inline-svg.ts and recording inlineSvgDependencies, Astro’s image pipeline only optimizes genuine raster assets like hero.jpg, while astro.config.ts remains completely free of manual .astro/data-store.json deletion hacks—unifying our Schema.org ImageObject Metadata Pipeline, Zero-Artifact Content-Addressable Build Caching, Single-Root JSON-LD 1.1 Knowledge Graph Validator, and Three-Band Adaptive SVG Color Governance.
References
- [MMJ18]Andrey Mokhov, Neil Mitchell and Simon Peyton Jones (2018). Build Systems à la Carte. Proceedings of the ACM on Programming Languages, 2(ICFP), pp. 79:1--79:29. DOI: 10.1145/3236774.
- [DJV04]Eelco Dolstra, Merijn de Jonge and Eelco Visser (2004). Nix: A Safe and Policy-Free System for Software Deployment. In Proceedings of the 18th USENIX Conference on System Administration (LISA '04), pp. 79--92. https://www.usenix.org/legacy/publications/library/proceedings/lisa04/tech/full_papers/dolstra/dolstra.pdf.
- [Mer88]Ralph Charles Merkle (1988). A Digital Signature Based on a Conventional Encryption Function. In Advances in Cryptology — CRYPTO '87 (LNCS, vol. 293), pp. 369--378. DOI: 10.1007/3-540-48184-2_32.