@moebel/image-url
v1.1.0
Published
Signierte imgproxy-URLs (URL-Bau + serverseitige Signierung) für den mde-images Bild-Service.
Readme
@moebel/image-url
Eine universelle TS-Lib zum Bauen signierter imgproxy-URLs (URL-Bau + serverseitige Signierung) — nutzbar in Next.js (Node & Edge) und beliebigen weiteren JS-Umgebungen.
Universell (Node + Edge + Browser-Worker)
Signiert mit Web Crypto (crypto.subtle) statt node:crypto, ohne
Buffer. Läuft damit ohne Anpassung in jedem Runtime mit Web Crypto:
- Next.js Node- und Edge-Runtime (Server Component, Route Handler, Middleware)
- Deno, Cloudflare Workers, Browser/Worker (nur dort, wo der Key serverseitig bleibt)
- Node ≥ 18
Da Web Crypto async ist, sind sign / signedImageUrl / buildPictureSources
Promises (await). buildPath / base64url / formatFromAccept bleiben sync.
Modulformat (ESM und CommonJS)
Das Paket ist ein Dual-Package: per exports werden sowohl ein ESM- als
auch ein CommonJS-Build ausgeliefert. Konsumenten brauchen also keinen
import()-Shim:
import { signedImageUrl } from "@moebel/image-url"; // ESM / TS (module: esnext|nodenext)const { signedImageUrl } = require("@moebel/image-url"); // CommonJS / ts-node (module: commonjs)Die Typen (index.d.ts) liegen für beide Conditions vor.
Was sie macht
- baut den imgproxy-Verarbeitungspfad (
rs:auto:W:H, optionalq:,dpr:) - kodiert die Quell-URL des Originals base64url als imgproxy-Quelle
- signiert den Pfad (HMAC-SHA256, base64url) — imgproxy-Referenz-Schema
- wählt das Format explizit per URL-Endung (
.avif/.webp/.jpg)
Ergebnis-URL:
https://<host>/<sig>/rs:<resize>:<w>:<h>[/q:<q>][/dpr:<n>]/<base64url(originalUrl)>.<ext>⚠️ Key-Safety (wichtig)
IMGPROXY_KEY und IMGPROXY_SALT sind Secrets und dürfen nie ins
Browser-Bundle. Diese Lib daher ausschließlich serverseitig nutzen:
Server Component, Route Handler / API, SSR oder Build-Step.
Ein next/image-loader läuft im Client und kann das Secret nicht halten —
deshalb gibt es zwei sichere Wege (siehe examples/next/):
| Variante | Datei | Idee |
|---|---|---|
| A (empfohlen) | MdeImage.tsx | Server Component rendert <picture> mit fertig signierten AVIF/WebP/JPEG-Quellen. Browser wählt das Format. |
| B | route-handler.ts | /api/img-Route signiert serverseitig und antwortet mit 302 auf die images.<zone>-URL; next/image zeigt auf diese Route. |
Format statt Custom Cache Key
Custom Cache Key ist bei Cloudflare Enterprise-only. Stattdessen ist jede Format-Variante eine eigene URL (Endung) und wird vom Edge per Default gecached. Format-Wahl:
- serverseitig pro Request über den
Accept-Header →formatFromAccept() - oder im Browser via
<picture>(Variante A) — bevorzugt, keinVary: Accept.
API
signedImageUrl({ host, originalUrl, width, height?, resize?, format?,
quality?, dpr?, key, salt }): Promise<string>
buildPath(opts): string // sync — signierbarer Pfad ohne Host/Signatur
sign(path, keyHex, saltHex): Promise<string>
base64url(input): string // sync
formatFromAccept(accept?): "avif"|"webp"|"jpg" // sync
buildPictureSources(base, formats?, dprs?): Promise<{ sources, fallback }>resize default auto (= contain-or-cover, skaliert nie hoch).
quality weglassen ⇒ serverseitige FORMAT_QUALITY (avif=50, webp=70, jpeg=75).
Beispiel
import { signedImageUrl } from "@moebel/image-url";
const url = await signedImageUrl({
host: "images.example.com",
originalUrl: "https://img.example.com/produkt/123/sofa.jpg",
width: 600,
height: 450,
format: "avif", // pro Browser-Support wählen
key: process.env.IMGPROXY_KEY!,
salt: process.env.IMGPROXY_SALT!,
});Build & Test
npm install
npm run build # -> dist/esm + dist/cjs (je mit type-Marker)
npm test # baut + node:test gegen dist/esm (ESM) UND dist/cjs (require)Der Test sichert den Signatur-Algorithmus per deterministischem Golden gegen
die öffentlichen imgproxy-Doku-Keys ab. Der authoritative End-to-End-Check
ist eine mit den echten Secrets signierte URL gegen den Live-Endpoint
(https://<host>/... → 200 + image/avif).
Hosts
host= der imgproxy-Serving-Host (images.<deine-domain>).originalUrl= die Quell-URL des Originals; muss zuIMGPROXY_ALLOWED_SOURCESpassen.
