Self-hosted, Sharp-powered runtime image transforms for Next.js.
npm install next-image-transformerSelf-hosted, Sharp-powered runtime image transforms for Next.js.
Next.js’s built-in image optimization (next/image + the /_next/image optimizer) is intentionally constrained and opinionated. If you want something closer to “Imgix/Cloudinary, but inside your Next.js app,” this package gives you:
- A route handler that fetches an upstream image, runs a small set of transforms via Sharp, and caches results on disk.
- A URL builder that creates transform URLs you can use from your app.
This tends to work best on managed app hosting where you run a Node runtime and can put a CDN in front:
- DigitalOcean App Platform (CDN-enabled)
- Render, Fly.io, Railway, Heroku
- AWS App Runner / ECS / EC2 (often fronted by CloudFront)
- Google Cloud Run (often fronted by Cloud CDN)
- Any setup fronted by Cloudflare / Fastly / etc.
#### 1) Install:
``bash`
yarn add next-image-transformer sharp
#### 2) Add a route handler
Create a route handler at some path, for example: src/app/image/route.ts
`ts
// src/app/api/image/route.ts
import { createImageTransformRouteHandler } from "next-image-transformer/server";
export const runtime = "nodejs";
const handler = createImageTransformRouteHandler({
// Must be an absolute URL. This is used to build a canonical URL for caching.
apiRouteUrl:
process.env.IMAGE_TRANSFORM_API_URL ?? "http://localhost:3000/api/image",
});
export const GET = handler;
`
#### 3) Create a URL builder module
Create a helper for example: src/lib/imageUrlBuilder.ts
`ts
import { createImageUrlBuilder } from "next-image-transformer";
export const imageUrlBuilder = createImageUrlBuilder({
// Same absolute URL as the route above, but safe to expose publicly
// if you want to build URLs client-side.
apiRouteUrl:
process.env.NEXT_PUBLIC_IMAGE_TRANSFORM_API_URL ??
"http://localhost:3000/api/image",
});
`
#### 4) Start writing URLs:
Then build URLs like:
`tsx
// src/components/MyImage.tsx
import { imageUrlBuilder } from "../lib/imageUrlBuilder";
export function MyImage() {
return (
src={imageUrlBuilder({
source: "https://images.example.com/cat.jpg",
fmt: "webp",
w: 800,
q: 80,
})}
/>
);
}
`
#### createImageTransformRouteHandler(options)
Import from: next-image-transformer/server
Returns: (req: Request) => Promise (compatible with Next.js Route Handlers)
Options
- apiRouteUrl: string (required)cacheDir
- Description: Absolute URL for the transform route (used to generate a canonical URL for caching).
- Default: none
- : string (optional)path.join(process.cwd(), ".transform-cache")
- Description: Directory on disk where transformed images are cached.
- Default: cacheControl
- : string (optional)Cache-Control
- Description: Value for the response header."public, max-age=31536000, immutable"
- Default: allowedHosts
- : Array (optional)source
- Description: Allowlist for the upstream URL host. If omitted, all hosts are allowed."images.example.com"
- Exact host: "localhost:3000"
- Host + port: /^(?:.+\.)?example\.com$/
- RegExp: (tested against both hostname and host)undefined
- Default: (allow all)
Behavior notes
- Format defaulting: if fmt is omitted in the URL, it defaults to "preserve".q
- Quality defaulting: if is omitted, the handler uses 100.w
- Resize semantics: if and/or h is provided, the image is resized with:fit: "inside"
- (or the provided fit)withoutEnlargement: true
- rotate()
- Auto-orient: Sharp is applied to respect EXIF orientation.
- Caching: responses are cached on disk; your runtime must have a writable filesystem.
#### createImageUrlBuilder(options)
Import from: next-image-transformer
Returns: a function (config: TransformConfig) => string that builds a transform URL.
Options
- apiRouteUrl: string (required)
- Description: Absolute URL for your transform route.
- Default: none
The transform URL uses these query params:
- source: string (required)http(s)
- Absolute URL to the upstream image.fmt
- : "preserve" | "webp" | "avif" (optional)"preserve"
- If omitted, it defaults to .w
- : number (optional)h
- 32-bit integer
- : number (optional)fit
- 32-bit integer
- : "cover" | "contain" | "fill" | "inside" | "outside" (optional)w
- Only used when resizing ( and/or h is provided)"inside"
- If omitted, it defaults to .q
- : number (optional)[0..100]
- Integer in 100
- Default:
See LICENSE.md`.