edgesharp
v0.1.4
Published
Next.js custom image loader for edgesharp — points <Image> at a Cloudflare Worker that does the optimization on Workers + R2.
Downloads
626
Maintainers
Readme
edgesharp
Cloudflare-native image optimization for Next.js. A drop-in replacement for the
default /_next/image endpoint, running on Cloudflare Workers with Zig WASM
SIMD for JPEG/PNG/WebP and a vendored libavif for native AVIF.
Live playground · Documentation
What it is
- One change in
next.config.mjsand every<Image>in your app routes through Cloudflare Workers instead of the default Next.js image optimizer. - 3-tier cache: Cache API → R2 → WASM transform. R2 egress is free, so the hot path is bandwidth-free.
- One Worker, one bundle.
src/worker.tsalways ships JPEG / PNG / WebP plus native AVIF via vendored libavif. ~838 KB gzip. Needs Workers Paid ($5/month per Cloudflare account); Workers Free is not supported (10 ms CPU/request, no Durable Objects). A singleDISABLED_FORMATSenv var (a comma-separated list, recognized values:jpeg,png,webp,avif,gif,svg) lets you drop any output format at runtime, flip it in the Cloudflare dashboard, no redeploy.DISABLED_FORMATS="avif"is the typical setting since AVIF encode is the most CPU-expensive.
Install
In your Next.js project:
pnpm add edgesharp// next.config.mjs
export default {
images: {
loader: "custom",
loaderFile: "./node_modules/edgesharp/dist/loader.js",
},
};Then point the loader at your deployed Worker URL via env var:
# .env.local (or your hosting platform's env config)
NEXT_PUBLIC_IMAGEMODE_URL=https://your-worker.workers.dev<Image> components stay exactly as written, srcSet, sizes, blur
previews, priority, fill mode all unchanged.
Prefer not to use an env var? Make a tiny custom loader file:
// app/edgesharp-loader.js
import { createLoader } from "edgesharp/loader";
export default createLoader({ url: "https://your-worker.workers.dev" });…and point loaderFile at that file instead.
Deploy your own
The button above takes you through Cloudflare's flow, fork this repo to your GitHub, connect Workers Builds, auto-create the R2 bucket and Durable Object, deploy. The pre-built WASM binaries are committed to the repo so the build doesn't need Zig.
After your first deploy, change a few things in your fork's wrangler.json:
ORIGIN: your Next.js app's origin URL. Path-relative?url=/foo.jpgparameters are fetched from here.ALLOWED_ORIGINS: by default"*"(so the demo's "paste any URL" playground works). Narrow this to a curated list of image hosts before putting the Worker in front of real traffic. Pair with Cloudflare Rate Limiting and Bot Fight Mode if it's publicly reachable.- Optional:
DISABLED_FORMATSin the Cloudflare dashboard. Comma-separated list of formats to drop (recognized:jpeg,png,webp,avif,gif,svg). For transformed outputs (jpeg/png/webp/avif), the negotiator skips disabled formats and picks the next-best one the browser accepts. For passthrough inputs (gif animation / svg), disabling rejects the source with 415. If every format the browser accepts is disabled, the Worker returns 415.DISABLED_FORMATS="avif"is the typical setting for watching CPU spend;DISABLED_FORMATS="svg,gif"refuses passthrough inputs.
Local development
pnpm install
pnpm run build # builds WASM + TS + demo
pnpm run dev # wrangler dev on :8787 (single bundle, libavif included)The WASM build needs Zig 0.16 locally if you change anything under
wasm/src/. The pre-built artifacts in src/wasm/ and wasm/vendor/ ship
with the repo so deploy-button users don't need Zig.
Costs
- Cloudflare Workers Paid - $5/month per Cloudflare account, 10M requests/month included, $0.30 per million after. Workers Free is not supported.
- R2 storage: $0.015 / GB-month; egress is free.
- No per-transform fees. After the first cold transform of each
(url, width, quality, format), repeat requests serve from R2 with free egress. CPU cost is per distinct variant, not per request, so crawler traffic on URLs you've already served doesn't scale costs the way per-transform pricing does. Compare to Vercel image optimization pricing.
Limitations
The decoder is built for the formats Next.js's <Image> actually serves -
not feature-parity with Sharp. Currently unsupported:
- CMYK JPEGs: print colorspace, rare on the web. Re-export as RGB.
- 16-bit PNG: uncommon for web; we decode 8-bit only.
- BMP, ICO: design choice, rare on the web.
- TIFF, HEIC, RAW (CR2/NEF/ARW/...): out of scope; these are professional formats that don't appear in
<Image>source files.
What we do handle that the Next.js default also handles: baseline + progressive JPEGs, PNGs, WebP (still and animated passthrough), GIF (still and animated passthrough), AVIF, SVG (passthrough with restrictive CSP). See Compatibility for the full side-by-side.
Companion Worker: edgesharp-og
The same repo includes og/, a separate Worker that generates
social share images (OpenGraph, Twitter, square thumbnails) from a meta
tag. Independent deploy, independent bundle (~1.6 MB gzip), independent
R2 bucket. Same $5/mo Workers Paid per-account covers both.
<meta property="og:image" content="https://og.example.com/og/">
<meta name="twitter:image" content="https://og.example.com/x/">The Worker reads the Referer header to know which page is being
shared, fetches that page’s <head>, substitutes its <meta>
tags into a bundled HTML template, and renders a PNG. Templates live
in og/src/templates/ — fork the repo, edit
the HTML, push to git; Workers Builds redeploys.
See og/README.md for the full URL contract,
allowed-origin setup, and template authoring guide.
Documentation
Full docs at https://edgesharp.teamchong.net.
- How it works
- Next.js integration
- Configuration
- Compatibility, what's supported vs Next.js's default loader
- Deployment
- Production hardening, what to set before linking the Worker URL publicly
- Architecture
License
MIT, see LICENSE.
