Skip to main content
<Image> renders a plain <img> pointing at Pylon’s built-in optimizer endpoint. The browser fetches an AVIF or WebP (or JPEG, depending on Accept) sized for the user’s viewport, served from disk cache on every subsequent request. The pipeline is written in Rust: image decodes the source, fast_image_resize (SIMD) resizes it, and ravif / rav1e (AVIF), libwebp, and mozjpeg encode the output. It needs no Sharp install, no libvips on the host, and no Node child process. Everything runs inside the Pylon binary.

What it does

For a single <Image src="/hero.jpg" width={1200} height={800} />:
  • Renders <img srcset> with multiple width candidates (default: 1x and 2x of width, capped at 3840px).
  • Each candidate points at /_pylon/image?src=/hero.jpg&w=<width>&q=<quality>.
  • Browser picks the smallest candidate that satisfies the viewport DPR.
  • Server decodes once, resizes with SIMD (Lanczos3), encodes as AVIF, WebP, or JPEG depending on Accept.
  • Result is hashed by (src, width, quality, format) and cached at .pylon/.cache/images/<hash>.<ext>. A cache hit skips decode and encode. It serves the file directly.
  • Response carries Cache-Control: public, max-age=31536000, immutable.

API

Examples

Hero image (above the fold)

priority skips loading="lazy" and sets fetchPriority="high" so the browser prioritizes it during the initial paint.

Responsive grid

The sizes attribute matters most. It tells the browser how wide the image renders at each breakpoint, so the browser picks the smallest viable srcset candidate. Without it, the browser assumes 100vw and downloads a 4K image for a 200px thumbnail.

Custom srcset

Useful for avatar grids where you know the exact DPR multipliers you need.

Safety & configuration

The optimizer only processes images that match its safety rules. Every setting has a safe default, and you tune each one through an environment variable. The same defaults apply in pylon dev and in production.

Allowed widths

The server rejects requests whose w value is not in its width set. This stops a cache-fill denial-of-service attack, where an attacker requests thousands of slightly different widths to fill your disk.
<Image> only generates srcset URLs with widths from this combined set, so it works with the default configuration. If you customize the environment variables, also pass widths={[...]} on each <Image> to keep the srcset URLs in range.

Allowed qualities

The server applies the same defense to the q parameter.
For finer control over quality, add more values: PYLON_IMAGE_QUALITIES=50,65,75,85,90,95.

Allowed formats

This locks the optimizer to a specific output set. The server picks the best match for the request’s Accept header from this list. <Image> lets the server pick by default. Pass format= in your own URLs for explicit control.

Remote source allowlist

Each entry is host or host/pathPrefix: Without this environment variable, any src starting with http:// or https:// returns 400. This prevents SSRF. It stops random visitors from making your server fetch arbitrary internal URLs.

Source size cap

This cap applies to both local files and remote fetches. The server rejects remote responses that exceed it mid-stream.

Pixel-bomb protection

A tiny PNG can declare a 100000×100000 canvas. Decoding it would allocate 40GB of RGBA data. Pylon reads the image header before decoding and rejects any source whose declared width × height exceeds this cap. The default (40M px, about 160MB peak decode) is tuned so a burst of concurrent requests cannot exhaust the memory of a small machine. Operators serving huge sources raise the cap explicitly.

Fetch timeout

This timeout applies to remote source fetches.

SVG handling

SVG is always rejected, both as input and as output. SVG can carry inline <script> tags and CSS. Serving arbitrary SVG through an optimization endpoint would create an XSS vector. There is no dangerouslyAllowSVG toggle. This is intentional. If you need SVGs, render them with a plain <img src="/icon.svg"> (not through <Image>) and serve them with Content-Security-Policy: script-src 'none'. See the unoptimized prop below.

Local file source

A src starting with / resolves under the frontend directory (PYLON_FRONTEND_DIR or <app>/web/dist). Pylon always rejects path traversal: it canonicalizes the path and checks the prefix, so .. segments and symlinks that point outside the directory return 400.

Bypassing the optimizer per-image

This renders a plain <img src="/icon.svg"> with no srcset and no optimizer round trip. It is useful for SVGs, animated GIFs, or images where the source is already sized correctly.

Format selection

Pylon serves AVIF automatically to browsers that advertise image/avif (Chrome, Firefox, Safari 16+). AVIF is the smallest of the three formats. The tradeoff is a slower cold encode (rav1e at speed 8), which the on-disk cache reduces to a one-time cost per (src, width, quality) combination. Browsers that do not advertise AVIF fall back to WebP, still about 30% smaller than mozjpeg at the same perceptual quality. Unknown clients (curl, or bots that send no Accept header) also get WebP.

Performance

Benchmarks on a 2400×1600 source JPEG (~250KB) on Apple Silicon: Tail latency is bounded by the encoder. Cache hits are bounded by your disk. For high-traffic sites, put a CDN in front of Pylon. Pylon never reprocesses an image once its immutable URL is cached.

Env reference

The cache lives at <cwd>/.pylon/.cache/images/. Delete it to force regeneration. There is no LRU eviction yet. Content-addressed entries are append-only, so the cache grows with the variety of (src, width, quality, format) combinations you serve. In production this is rarely a problem: the full set of a site’s images is usually under 1GB. A pylon cache clean command will arrive when it matters.

Differences from Next.js’s <Image>

placeholder="blur" is on the roadmap. SVG support is deliberately off the roadmap. Render SVG with a plain <img> tag, not through <Image>.