Skip to content

Serve compressed WebP images - #1210

Merged
ajwild merged 8 commits into
mainfrom
image-webp-compression
Aug 21, 2026
Merged

Serve compressed WebP images#1210
ajwild merged 8 commits into
mainfrom
image-webp-compression

Conversation

@ajwild

@ajwild ajwild commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

The docs site serves every screenshot as its original PNG, straight from content/. That is 78 MB of images, and pages routinely ship a 1.5 MB PNG into a 620px-wide column. Hugo can resize and re-encode these itself, but only for images it can resolve as resources — which means every image has to go through one code path first.

So this is mostly plumbing, with the compression as the last step:

  • Published image bytes: 78 MB → 9.8 MB. Total public/ 126 MB → 55 MB.
  • Images are served as WebP at quality: 80 with Lanczos resampling, in two variants (800w + 1600w, or W + 2W when a page sets width=). The 2× variant is skipped when the source has no headroom for it.
  • sizes encodes the real CSS caps from article.scss: 620px at widescreen, 750px at ultrawide, and ~983px just below the 1024px breakpoint, which is the widest the column ever gets. .page__toc is position: fixed and hidden below widescreen, so a missing table of contents never widens the column.
  • Originals are no longer copied into public/ at all — cascade sets build.publishResources: false, so a resource publishes only when something calls .RelPermalink on it.

Commits

Each one builds cleanly on its own, so the history is bisectable. That mattered: building them in isolation is how I found that the download shortcode needs the contentassets mount, which I had originally put one commit too late.

chore(images): render images with the img shortcode 44 markdown images → {{< img >}} across 21 files
fix(release-notes): host the 2021 release note images in the repo 11 images that pointed at user-images.githubusercontent.com
fix(li-document-references): restore the missing UI screenshot the one genuinely dead image reference on the site
feat(shortcodes): add a download shortcode for file links must precede the config change, or the two .patch files 404
feat(images): serve resized WebP variants the config, partials, and shortcode rewrite
feat(images): reject markdown images in favour of the img shortcode build guard, see below
chore(images): remove orphaned images 21 unreferenced files, 8.3 MB
fix(assistants): drop the hardcoded image height 320x320 on a 539×522 source

Paths are translated, not copied, in the first commit: a markdown destination resolves relative to the page URL, the shortcode relative to the source file, which is one level shallower for a non-bundle page. So ../images/li-enum-ui.png becomes images/li-enum-ui.png.

The markdown-image guard

render-image.html now fails the build instead of rendering:

ERROR Use the img shortcode instead of a markdown image for "nope.png",
referenced from ".../content/operations/architecture.md:34:1"

A permissive render hook cannot actually fix a stray markdown image, because every llms.txt layout renders through .RenderShortcodes, which expands shortcodes but never runs Goldmark hooks. A hook would make the image look right in HTML while leaving it pointing at an unpublished original in llms.txt — broken with nothing to surface it. Failing at authoring time is the honest option.

It caught 4 images my conversion script had missed, in media-library-setup/index.md.

Two limits worth knowing: it only catches ![](…), not raw <img> HTML, which passes through untouched under unsafe: true; and the first offender aborts the build rather than collecting them all.

Orphan removal

I used Hugo as the oracle rather than grep: under publishResources: false, an image that never reaches public/ is unreferenced. Built with --buildDrafts --buildFuture so the 3 draft pages counted, then diffed source images against published output. After deleting all 21, the published file set is byte-for-byte identical — 412 files, 55 MB, same list.

Grep alone would have got this wrong. 8 of the 21 have same-named twins that are referenced from other directories (print-*.png, k-menu-assistants.png, image-editor*.png); I confirmed each twin exists and compared bytes. Seven are identical leftovers from doc moves, and for k-menu-assistants.png the referenced copy is the newer screenshot.

Alt text

alt="image" is now at zero, and no {{< img >}} is missing an alt. I wrote 25 descriptions by opening each image, including the 5 architecture diagrams that had no alt attribute at all.

Verification

Full build: 0 warnings, 0 errors. 1017 image URLs resolve against published files, 0 broken; 267 llms.txt references, 0 broken; both .patch downloads publish and resolve. main for comparison has 1 broken image and 1 broken llms.txt reference, both fixed here.

Not addressed

  • 8 changed content files fail prettier --check — all of them already fail on main. Reformatting them would bury the real diff in whitespace noise.
  • <p><figure> nesting (invalid but harmless) went from 7 to 18 occurrences. It is a pre-existing artefact of the theme's RenderString paths, not something these commits introduce.
  • content/reference/document/metadata/plugins/li-list-reference.md now has no screenshot. Its image had been unreferenced since the main-nav restructure, so removing it was right, but the page may want a fresh one.

ajwild and others added 8 commits August 21, 2026 10:45
Markdown paths resolve against the page URL and shortcode paths against the
source file, so the paths shift one level. Also replaces placeholder alt text
with descriptions, including the architecture diagrams that had none.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Resolving the file as a resource is what publishes it, which plain markdown
links do not do. Needs the content directory mounted into assets so
resources.Get can reach files that sit next to a page.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Originals are no longer published, so anything referencing an asset must go
through .RelPermalink. That is why illu-teaser now resolves its SVGs through
the partial instead of emitting a raw path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A render hook cannot fix llms.txt, which is built with .RenderShortcodes and
never runs hooks, so a markdown image would look fine in HTML while staying
broken there. Catches ![](…) only, not raw img tags.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Leftovers from earlier restructures and from reference pages deleted upstream.
Seven were byte-identical duplicates of images still in use elsewhere.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The source is 539x522, so 320x320 described the wrong shape. Omitting it
lets the shortcode scale the height from the width instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ajwild
ajwild force-pushed the image-webp-compression branch from e2c8f2d to 8900ff9 Compare August 21, 2026 08:53
@ajwild
ajwild merged commit 89f5e8b into main Aug 21, 2026
1 check passed
@ajwild
ajwild deleted the image-webp-compression branch August 21, 2026 08:55
@livingdocs-automation

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 1.158.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants