Skip to main content
If you deploy on Vercel, every image 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
Find your project’s id with a quick request against GET /v1/projects:
The response lists every project your key can see, each with its id, slug and publicUrl.

Serve files that are already in PixGlory

Tell Next.js’s image component that cdn.pixglory.com is a trusted remote host, in next.config.ts:
next.config.ts
That alone lets 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:
If most of the images on a page come from PixGlory, a custom loader is less to repeat. A loader that just returns the URL unchanged skips Next’s optimiser for every image that uses it, without an 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
From the client, presign, upload the bytes straight to R2, then confirm:
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

A 402 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:
Branch on 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.