Image Optimization
Crax provides two image components for different use cases.
<Image />
CDN-aware responsive images via @unpic/react. Auto-detects image CDNs (Cloudinary, Imgix, etc.) and generates optimal srcset attributes.
Props
src(required): URL or import.alt(required): Alt text.width,height: Dimensions for fixed/constrained layout.layout:"constrained"(default),"fixed", or"fullWidth".background: Blur-up placeholder. Defaults to"auto".priority: LCP hint — eager loading + high fetch priority + (React 19) a preload link. See Priority (LCP) images below.
Width/height are required
width+height (or aspectRatio) are enforced at compile time on every layout except "fullWidth" — this is a CLS (layout shift) guard, not a suggestion:
layout="fullWidth" doesn't take width (it's set by the container, so the type disallows it). <Picture>'s string-src branch has the same width/height requirement — its vite-imagetools object-src branch is exempt because real dimensions are read from the import at build time.
Local /public images
Plain string URLs to /public assets (or any URL outside a recognized CDN) are not processed by unpic: no generated srcset, no format conversion, and no automatic loading="lazy"/decoding="async" — those props pass straight through unchanged, so set them yourself if you want them. For real optimization of local files, import them through vite-imagetools instead:
<Picture />
For vite-imagetools ?as=picture imports with multiple format sources:
Renders a <picture> element with <source> tags for each format, with fallback <img>. A plain string src also renders (falls back to a plain <img>), but then width/height are required — a string has no known intrinsic size for Crax to infer, while the vite-imagetools object src infers them from src.img.w/h.
Priority (LCP) images
priority sets loading="eager" + fetchPriority="high" (default is lazy), and — on React 19 — calls preload() from react-dom (string src only) so the browser starts fetching the image before React commits. Reserve it for the single above-the-fold image most likely to be the page's Largest Contentful Paint.
Blur-up placeholder for local images
background="auto" only works for CDN-recognized URLs — unpic has no way to generate a placeholder for a local file. For local images, build a tiny inlined blur placeholder at compile time with vite-imagetools and pass it straight to background (Image) or placeholder (Picture):
w=24— tiny output, cheap to inlineblur=3— Gaussian blur sigma, applied at build time (not a runtime CSSfilter: blur())format=webp— small, broadly supportedinline— base64-encodes the transformed image into the module instead of emitting a file, so the import resolves to adata:image/webp;base64,...string with no extra network request
The import resolves to a bare data-URI string (no url(...) wrapper needed) — Image's background and Picture's placeholder auto-wrap it. Plain CSS values ("auto", colors, gradients) still pass through unchanged. See .crax/image/usage.md for the full directive breakdown.
Lazy Loading
CDN and vite-imagetools images default to loading="lazy" and decoding="async" via unpic. Plain local /public string sources do not get these defaults automatically (see Local /public images above) — set them explicitly if needed. Override for above-the-fold images:
Configuration
Only images.formats[0] is actually wired: vite.config.ts passes it to imagetools({ defaultDirectives }), so a directive-less local import (import x from './photo.jpg', no ?w=… query) automatically resolves to that format. Request additional formats explicitly per-import with ?format=webp;avif&as=picture.
images.deviceSizes and images.defaultProps are declared in the config type but not wired into anything yet — reserved for a future responsive-breakpoints/defaults pass. Don't rely on them.
