Docs Writing
Images & code
What happens to an image and a code block between the Ghost editor and the page — blur-up placeholders, responsive sources, and syntax highlighting.
Two things in a post are rebuilt rather than passed through. Neither needs any syntax; both are worth knowing about because their failure modes are quiet.
Images
Content images uploaded to Ghost are rewritten to proxy-relative URLs and then enriched at build time.
Blur-up. Each image is fetched once during the build, downscaled to a ~24px blurred WebP, and inlined as a base64 data URI on a wrapper element. The real image crossfades over it on load. A reader sees a soft preview sharpen instead of a blank box that shifts the layout when it fills.
Responsive sources. Intrinsic width and height come from the same probe, so
the browser reserves the right box before the image arrives. A srcset is built
from the proxy’s width parameter.
All of it is paid for at build time on a prerendered blog — no extra round-trips for the reader.
It is deliberately resilient. Any fetch or decode failure leaves the original tag untouched: no placeholder, just a plain image. Results are memoised per build, so a repeated URL is fetched once and a broken one is not retried. This means a missing blur-up is invisible on the page and only shows in the build log — if an image lands hard on a slow connection, check there.
The probe resolves proxy-relative URLs against production, because a build-time fetch has no local server to hit. Images that only exist on a local Ghost instance will not get placeholders.
Code blocks
Ghost’s code cards are extracted from the HTML and re-rendered, rather than
styled in place. The language comes from the language- or lang- class Ghost
emits, falling back to a data-language attribute and then to text.
Set the language in the Ghost editor’s code card. An unlabelled block renders as plain text — legible, but unhighlighted.
Highlighting is dual-theme: both palettes ship as custom properties on each token and the theme class picks a side, so switching light/dark never re-renders or flashes. The same contract is used by the code blocks on this page.
Everything inside a code block is masked before the directive passes run, so a
fenced example containing [!poem] or [^ref] stays an example.
Notes
- Blur-up:
src/features/posts/server/blur-up.ts - Responsive sources:
src/lib/media/responsive-image.ts - Code extraction:
src/features/posts/server/code-blocks.ts - The crossfade and player wiring:
src/features/posts/client/prose.ts