next/image serves through its own optimiser is billed
per transformation. When the image already came out of PixGlory at the right size and format,
that second optimisation pass buys you nothing and costs you twice. This guide covers both ways
to use PixGlory from a Next.js app: pointing next/image at files already sitting in a CDN
project, and uploading new files from a route handler.
Before you start
You need a PixGlory API key and a CDN project. See the Quickstart to create a key, and create a project from your dashboard if you don’t have one yet. Add your key and project id to your environment:.env.local
GET /v1/projects:
id, slug and publicUrl.
Serve files that are already in PixGlory
Tell Next.js’s image component thatcdn.pixglory.com is a trusted remote host, in
next.config.ts:
next.config.ts
next/image render a PixGlory URL. The part that saves the Vercel billing is
telling it not to re-optimise what we already optimised. The simplest way is unoptimized on
the individual image:
unoptimized prop on each one:
lib/pixglory-loader.ts
Upload new files from a route handler
When a user uploads a file through your app, presign and confirm it against PixGlory from a route handler rather than shipping your API key to the browser. The upload itself (the PUT to R2) still happens client-side, straight to the presigned URL, so the bytes never pass through your server.app/api/pixglory/presign/route.ts
app/api/pixglory/confirm/route.ts
lib/upload-to-pixglory.ts
confirmed[0].publicUrl is the file’s permanent, canonical PixGlory link. Persist it (in your
database, your CMS, wherever the rest of the record lives) rather than reconstructing it, since
that is the only URL form the API guarantees.
A single presign request can name several files at once (
files: [...]), and uploads comes
back in the same order. Batch a gallery upload into one call instead of one round trip per
file.Handling errors
A402 response means the organisation’s storage quota would be exceeded; a 429 means you’ve
hit the rate limit and should back off using the Retry-After header. Every error body has the
same shape:
error.code, not error.message, which can change wording without notice. The full
list of status codes and error codes is in the API Reference tab.