ditherkit

Choose your package

A decision table for picking the right ditherkit package for your job.

The toolkit offers two places to run the dithering work: in the browser (@ditherkit/react) or on the server (@ditherkit/next). This page is a decision table for picking between them.

The short version

  • Interactive, user-adjustable, previews@ditherkit/react
  • Single static image, fixed size@ditherkit/next with <DitheredImageSSR />
  • Responsive image that switches resolution per viewport@ditherkit/next with <DitheredPicture />
  • Neither of those, or you're not using React@ditherkit/core directly

The full decision table

If you need…Use
Real-time slider adjustments by the user@ditherkit/react
Live preview of a file the user just uploaded@ditherkit/react
A playground where the user picks algorithm and palette@ditherkit/react
A single dithered image at a fixed size<DitheredImageSSR /> from @ditherkit/next
Responsive dithered images in grids, cards, fluid layouts<DitheredPicture /> from @ditherkit/next
Different dither settings per viewport (art direction)<DitheredPicture /> with per-variant overrides
Dithered images as part of your static marketing pages@ditherkit/next
SEO-indexable dithered content@ditherkit/next
CDN-cached, long-lived image URLs@ditherkit/next
Dithered <img> tags that work without JavaScript@ditherkit/next
Zero client-side JavaScript for images@ditherkit/next
A Node build script that outputs dithered files@ditherkit/core + sharp
Cloudflare Workers / edge runtime@ditherkit/core + a bundler-friendly image decoder
SvelteKit / Vue / Solid / non-React framework@ditherkit/core directly
The smallest possible bundle@ditherkit/core + your own integration

DitheredImageSSR vs DitheredPicture

Both are in @ditherkit/next, both are server-rendered with zero client JS. The difference is responsiveness:

<DitheredImageSSR /><DitheredPicture />
RendersSingle <img> via next/image<picture> with multiple <source>
ResponsiveCSS scaling only (width: 100%)True resolution switching per viewport
Art directionOne set of dither paramsDifferent params per breakpoint
Best forHero images, fixed-size slotsGrids, cards, fluid layouts

Use <DitheredImageSSR /> when the image has one fixed output resolution. Use <DitheredPicture /> when you need different dithered variants at different viewport widths — especially in layouts where CSS scaling would either waste bandwidth or show blurry upscaled pixels.

See <DitheredPicture /> for the full reference, including computeVariants for auto-generating breakpoints from your layout function.

Why not "just both"?

You can absolutely use both in the same Next.js app — see the example-next app, which demonstrates exactly that. You'd want both when:

  • Your landing page uses @ditherkit/next for fast, cached, above-the-fold imagery
  • Your playground / upload / editor page uses @ditherkit/react for interactive adjustments

The packages coexist cleanly. Just remember to install both:

pnpm add @ditherkit/next @ditherkit/react sharp

And import from the matching package — @ditherkit/next does NOT re-export @ditherkit/react.

Why not "just server"?

Server-side rendering is wonderful for anything static, but it can't do real-time updates. Every parameter change would require a new server round-trip, a new Sharp pass, and a new HTTP response. Even with ISR caching, the first render is slow and the feedback loop on a slider would be terrible.

The Web Worker model in @ditherkit/react processes images in the browser in a few milliseconds per pass, with no network involvement once the source image is cached. That's the right model for interactivity.

Why not "just client"?

Client-side rendering means:

  • The source image is always fetched by the browser. No server-side proxy for CORS-restricted sources.
  • The processing runs on every user's device. Slow devices get slow rendering.
  • The output never gets cached at the edge. Every user re-processes the image from scratch.
  • No SEO. Search engine crawlers see a blank <canvas>, not the dithered content.

For anything above the fold on a marketing page, or any image that's the same for every user, the server-rendered path is dramatically better.

Performance comparison

For a 400×300 image with Floyd-Steinberg dithering:

@ditherkit/react (client)@ditherkit/next (server, first hit)@ditherkit/next (server, cached)
First paint~50ms (source fetch + decode + worker)~100ms (fetch + Sharp + network)~5ms (HTTP cache hit)
Slider drag update~10msN/A — no live interactionN/A
Subsequent page loadsFull reprocess per loadCache hitCache hit
Bandwidth per viewSource image sizeWebP output size0 (cached)

The client case is fast for the first interaction but re-processes on every page load. The server case has a slower first hit but every subsequent view is essentially free.

Rule of thumb: if an image is the same for every user and doesn't change interactively, serve it with @ditherkit/next. If it changes per-user or per-interaction, use @ditherkit/react.

When to use @ditherkit/core directly

Both @ditherkit/react and @ditherkit/next are opinionated wrappers around @ditherkit/core. They're great if their opinions match your use case. When they don't, @ditherkit/core is right there:

  • Custom pipelines — you want to interleave dithering with other image operations
  • Unsupported frameworks — Svelte, Vue, Solid, Qwik all work fine with the raw algorithms
  • Build scriptspnpm build:images that processes a folder of source files at CI time
  • Unusual runtimes — Cloudflare Workers, Deno, Bun edge functions, WebAssembly hosts
  • Minimal bundles — if your bundle size matters down to the KB, skipping the wrapper lets you include only what you need

The algorithms are pure functions taking a pixel buffer. There's no hidden state, no React dependency, no DOM requirement. Drop them in anywhere.

On this page