Skip to content

Images

Responsive images with no optimizer to run — unpic for a CDN, imagetools for files in the repo.

Updated View as Markdown

There is no image component and no image server. next/image is two things glued together: a component that writes srcset and sizes for you, and an optimizer that resizes on the fly — a process with sharp in it, a cache to manage, and a CPU bill on every cold hit. The first half is worth having. The second belongs to whatever already serves your images.

Images on a CDN

If the file lives on Cloudinary, imgix, Cloudflare Images, Bunny, Vercel, Netlify or any of the other providers unpic knows, it already has a resizing url. unpic writes the srcset against it:

src/app/page.tsxtsx
import { Image } from '@unpic/react';

<Image
  src="https://res.cloudinary.com/demo/image/upload/sample.jpg"
  layout="constrained"
  width={800}
  height={600}
  alt="A sample"
/>

It is a plain component, so it renders in a server component and the browser receives an <img>srcset from 640w up, sizes, aspect-ratio, loading="lazy", decoding="async" — and none of unpic’s code. The route’s javascript does not change. The CDN is detected from the url; nothing to configure.

TanStack Start and Astro point at the same library, for the same reason.

Images in the repository

A file under src/ is resized once, at build time, by vite-imagetools. Query parameters on the import say what you want:

vite.config.tsts
import { imagetools } from 'vite-imagetools';

export default defineConfig({
  plugins: [imagetools(), rscKit(), ...],
});
src/app/page.tsxtsx
import hero from '../hero.png?w=400;800;1200&format=webp&as=srcset';
import heroSrc from '../hero.png?w=800&format=webp';

<img srcSet={hero} src={heroSrc} sizes="(min-width: 800px) 800px, 100vw" width={800} height={600} alt="…" />

The build emits hero-<hash>.webp at each width into assets/, hashed and cacheable forever, and a page that is frozen carries the urls. It costs the build what resizing costs, once per image per width, and nothing at request time. It is opt-in for that reason: a hundred hero images at four widths is a noticeable build, and most of them belong on a CDN.

Declare the query so TypeScript stops asking:

src/images.d.tsts
declare module '*?*' {
  const value: string;
  export default value;
}

What to pick

the image is use
user-uploaded, or on a CDN already unpic
in the repo, a handful imagetools
in the repo, hundreds put them on a CDN and use unpic
an icon or a logo <img>, or inline the svg

Neither one runs at request time, and neither is on the page’s javascript.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close