@rsc-kit/mcp
Version:
An MCP server over what an rsc-kit build decided: the routes, why each one is static or not, and what it costs the browser.
90 lines (69 loc) • 3.03 kB
Markdown
# Images
> Responsive images with no optimizer to run — unpic for a CDN, imagetools for files in the repo.
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](https://unpic.pics/img/react/),
it already has a resizing url. [unpic](https://unpic.pics) writes the `srcset`
against it:
```tsx title="src/app/page.tsx"
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](https://github.com/JonasKruckenberg/imagetools). Query
parameters on the import say what you want:
```ts title="vite.config.ts"
import { imagetools } from 'vite-imagetools';
export default defineConfig({
plugins: [imagetools(), rscKit(), ...],
});
```
```tsx title="src/app/page.tsx"
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 queries so TypeScript stops asking — and the build's typecheck
lets them through:
```ts title="src/images.d.ts"
// A pattern may hold one `*`, so each matches on the tail of the query.
declare module '*&as=srcset' {
const srcset: string;
export default srcset;
}
declare module '*&format=webp' {
const url: string;
export default url;
}
```
## 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.