DocumentationFramework guide

Image optimization

@briansunter/nib-images is an optional build-time package. It creates static responsive image markup and files; it does not add a client runtime or turn an image into a React island.

Install it:

npm install @briansunter/nib-images

Register the plugin in nib.config.ts:

import { defineConfig } from '@briansunter/nib'
import { images } from '@briansunter/nib-images/plugin'

export default defineConfig({
  site: { title: 'Example' },
  plugins: [images({ formats: ['avif', 'webp'] })],
})

Import a local image with the explicit query and render it with Image:

import { Image } from '@briansunter/nib-images'
import hero from './hero.jpg?nib-image'

export default function Page() {
  return <Image src={hero} alt="Hikers on a ridge" layout="full" priority />
}

Image reads width and height from the import, including EXIF orientation. It emits AVIF and WebP <source> elements plus JPEG (or PNG for alpha) fallback markup. Standard images use loading="lazy" and decoding="async"; priority uses eager loading and a high fetch priority.

quality applies to AVIF, WebP, and JPEG. PNG fallback output stays lossless and uses a fixed compression setting, so a misleading PNG quality control is not exposed.

Layouts and sizes

Layout Use Default sizes
constrained A responsive image with a known maximum width (max-width: Wpx) 100vw, Wpx
fixed An icon or fixed-size image Density descriptors, no sizes
full An image spanning its container 100vw

Use an explicit sizes value when CSS places an image in a multi-column layout. Nib cannot infer arbitrary container widths. widths replaces the configured responsive ladder, while fixed uses densities instead.

Cache and limits

Transformed files are content-addressed in .nib/cache/images and linked or copied to dist/client/assets/nib. Deleting dist is safe; a warm build reuses checksum-validated cache entries. concurrency bounds all active Sharp transforms, while memoryLimitMb can lower that bound using a conservative per-transform estimate for CI. The automatic value respects both available processors and Node's libuv image-task concurrency. Nib maintainers can run bun run benchmark:images to compare cold, warm, concurrency, memory, and WebP-only versus AVIF-plus-WebP behavior on the current machine.

In development, every imported image is watched explicitly. Changed bytes are re-inspected without restarting the server and receive a new ETag; touching or byte-identically rewriting a file keeps the same key and returns 304 without re-encoding. Short editor overwrite windows are retried instead of discarding the last valid source.

The root component entry is processor-free. Import images() from the /plugin entry as shown above. Image is a static rendering primitive and cannot be imported into a React island; build and development report that mistake instead of shipping build paths or failing during hydration.

For imported content or Markdown that already uses stable public image URLs, configure an opt-in source root. Nib copies the originals to that public prefix for lightboxes and fallbacks, then rewrites matching rendered <img> elements to the same responsive <picture> pipeline:

images({
  content: [{
    publicPath: '/site-assets/',
    directory: 'src/assets/site-assets',
    widths: [320, 640, 1280],
    sizes: '(min-width: 900px) 860px, 100vw',
  }],
})

This is deliberately reference-driven rather than a scan of every file in the project. Animated, SVG, or unsupported/corrupt sources remain available at their original URL and do not abort the rest of the build.

Only local JPEG, PNG, WebP, and AVIF sources are transformed in this release. SVG and animated sources are passed through without rasterization or animation conversion. Remote URLs and automatic relative-path Markdown image resolution are not yet supported.