Images
Adding an image
Put the file next to the page that uses it and reference it relatively:
A path starting with / is resolved from the root of the content directory
instead, which is what you want for an image several pages share:
Either way Chronicle rewrites the URL at build time and appends a hash of the file's contents. That hash is why an image can be cached forever by a browser and still update the moment you change the file.
Images in public/ are different — they are served exactly as you put them
there, with no rewriting and no optimization. Use public/ for a favicon or a
download, and keep content images next to their pages.
Optimization
Everything below happens on its own. There is nothing to turn on.
Content images are resized, re-encoded to a modern format, and cached on disk,
through an on-demand /api/image endpoint.
How it works
- The remark plugin rewrites all image URLs to route through
/api/image - On first request, the image is resized and converted based on browser support
- The result is cached — subsequent requests are served instantly
- On server start, all content images are pre-cached (warmup)
Format Negotiation
The endpoint reads the browser's Accept header and serves the best format:
| Browser sends | Format served |
|---|---|
Accept: image/avif | AVIF |
Accept: image/webp | WebP |
| Neither | Original (resized only) |
SVG images are passed through unchanged.
Query Parameters
Control image output directly in markdown using query parameters:
Width (w)
Sets the output width in pixels. The image is resized proportionally (height auto-calculated). Images are never enlarged beyond their original size.
Allowed values:
| Width | Use case |
|---|---|
320 | Small thumbnails, mobile icons |
640 | Mobile content images |
768 | Tablet content images |
1024 | Default — desktop content |
1280 | Wide content areas |
1536 | Large displays |
1920 | Full-width hero images |
Default: 1024
Quality (q)
Sets the compression quality. Lower values produce smaller files with more compression artifacts.
Allowed values:
| Quality | File size | Use case |
|---|---|---|
60 | Smallest | Thumbnails, previews |
75 | Balanced | Default — content images |
90 | High quality | Screenshots with text |
100 | Maximum | Lossless-like output |
Default: 75
Examples
<!-- Default: 1024w, quality 75 -->

<!-- Small thumbnail -->

<!-- High quality screenshot -->

<!-- Width only, default quality -->

<!-- Quality only, default width -->
API Endpoint
GET /api/image?url=/_content/docs/photo.png&w=640&q=75| Parameter | Required | Description |
|---|---|---|
url | Yes | Content image path (must start with /_content/) |
w | Yes | Output width (must be an allowed value) |
q | No | Quality 1-100 (snapped to nearest allowed value) |
Response Headers
Content-Type: image/webp (or image/avif, or original mime)
Cache-Control: public, max-age=31536000, immutable
Vary: AcceptCache
Optimized images are cached using Nitro storage with the fs driver at .cache/images/. Cache persists across server restarts.
The warmup process pre-caches all content images as WebP at default width (1024) and quality (75) when the server starts.