Next.js
Configure next/image with a Keenpix custom loader, responsive sizes, managed or self-hosted delivery, origin controls, and production validation.
next/image lets you swap its optimizer for a custom loader. Point it at Keenpix and keep
using <Image> exactly as before — Next still generates the srcset, Keenpix serves
each width.
Use this setup when you want image transformation and delivery to remain separate from the Next.js server or hosting platform. Keep the built-in optimizer when it already meets your deployment, cache, and cost requirements. A custom image CDN adds another service boundary, so the gain must justify the URL migration, cache policy, and failure mode.
1. Add the loader
// keenpix-loader.js
export default function keenpixLoader({ src, width, quality }) {
const baseUrl = process.env.NEXT_PUBLIC_KEENPIX_BASE_URL
const project = process.env.NEXT_PUBLIC_KEENPIX_PROJECT
const origin = process.env.NEXT_PUBLIC_IMAGE_ORIGIN ?? ''
// next/image passes `src` as-is; Keenpix needs an absolute URL.
const url = src.startsWith('http') ? src : `${origin}${src}`
const params = new URLSearchParams({ w: String(width), fmt: 'auto' })
if (project) params.set('project', project)
if (quality) params.set('q', String(quality))
return `${baseUrl}/img/${encodeURIComponent(url)}?${params.toString()}`
}Use the canonical managed-cloud path without a project query:
NEXT_PUBLIC_KEENPIX_BASE_URL=https://cdn.keenpix.com/p/YOUR_PROJECT_ID
NEXT_PUBLIC_IMAGE_ORIGIN=https://assets.example.comFor a self-hosted instance, set NEXT_PUBLIC_KEENPIX_BASE_URL to your image host and
set NEXT_PUBLIC_KEENPIX_PROJECT; the loader adds that project id to the query.
2. Point next.config at it
// next.config.js
module.exports = {
images: { loader: 'custom', loaderFile: './keenpix-loader.js' },
}3. Use <Image> normally
import Image from 'next/image'
export default function Hero() {
return <Image src="/hero/spring.jpg" width={1920} height={1080} alt="Spring" />
}Add the image origin host to the project's allowlist (Settings → Security). Set
NEXT_PUBLIC_KEENPIX_BASE_URL and NEXT_PUBLIC_IMAGE_ORIGIN in .env, plus
NEXT_PUBLIC_KEENPIX_PROJECT on self-hosted deployments.
Set sizes for responsive layouts
The loader receives each width that Next.js puts into the generated srcset. The browser
still needs an accurate sizes value to choose among those candidates. Without it, a
responsive image can be treated as viewport-wide and download more pixels than its actual
slot needs.
<Image
alt="Canvas backpack in olive green"
height={1200}
sizes="(max-width: 768px) 100vw, 50vw"
src="/products/backpack.jpg"
width={1600}
/>Keep width candidates aligned with real component breakpoints. More variants are not automatically better: they create more cache keys, and adjacent widths that produce almost the same byte size add little value.
Static export and non-Vercel deployments
A custom loader returns ordinary external image URLs, so it also works when Next.js emits a
static export and no Next.js image-optimization server exists at runtime. Keenpix still has
to be reachable from the browser, and every relative src still needs an absolute
NEXT_PUBLIC_IMAGE_ORIGIN.
For managed delivery, the public project id belongs in the
cdn.keenpix.com/p/<project-id> path. It is routing context, not a secret. For self-hosting,
the project query parameter selects the project on your own image host. The transform URL
concepts are similar, but moving between the two paths still requires hostname, cache, and
configuration planning.
Protect the source and URL grammar
Keenpix only fetches from hosts in the project's origin allowlist. Start with the narrowest set of source hosts your application actually uses and verify that an unapproved host fails. The fetcher also blocks private/internal addresses and revalidates redirects, but the allowlist remains the first product-level boundary.
Do not put an HMAC signing secret in keenpix-loader.js; the loader is browser-facing. If
your project requires signed transform URLs, sign them on a server you control or route the
request through a server-side signing endpoint. Signatures are useful when third parties
could append parameters and create expensive cache misses. They do not stop someone from
copying and replaying an already valid public image URL.
Validate the integration before launch
Check the result as a delivery pipeline, not only as a rendered image:
- Inspect the final
<img>and confirmsrcsetcontains several Keenpix URLs with differentwvalues. - Resize the viewport and verify the browser selects a candidate appropriate to the rendered slot.
- Check
Content-Type,Vary, andCache-Controlon AVIF, WebP, and fallback responses. - Confirm an unapproved source host is rejected.
- Compare decoded output and transferred bytes with your own source files and settings.
- Test an uncached request, a repeat request, an origin failure, and the rollback path.
fmt=auto varies the response by the browser's Accept header. Every cache in front of the
image service must keep those variants separate. If the outer CDN cannot vary safely, use
explicit AVIF/WebP URLs and provide a fallback through <picture> instead.
Decide on cost and ownership
Next.js and Vercel's built-in image optimization can be the simplest option because there is no new provider or URL migration. Keenpix is a better candidate when the team wants one image URL grammar across frameworks, a separately measured delivery layer, published managed-delivery allowances, or a future self-host path. Neither architecture is universally cheaper or faster; model requests, cache reads/writes, delivered bytes, origin behavior, and engineering time from your own workload.
- Review the managed image CDN pricing.
- Compare Keenpix with Vercel Image Optimization.
- Read the deeper Next.js custom-loader production guide.
- If you own the operations stack, review self-hosted image CDN deployment.