Vue Storefront is now Alokai! Learn More
Image Optimization

Image Optimization

Serve automatically resized, compressed, and format-optimized images through the Alokai Image Optimizer - via the createImageOptimizer helper from @vue-storefront/next on Next.js, or the @vue-storefront/nuxt module on Nuxt.

Product images delivered by commerce platforms are usually full-size originals. The Alokai Image Optimizer sits at the edge in front of your storefront and transforms images on the fly - resizing them to the requested width, adjusting quality, and serving modern formats like WebP or AVIF based on the browser's capabilities.

The integration plugs into the framework's image component (Next.js <Image>, Nuxt @nuxt/image) and produces:

  • a custom image loader that rewrites media-host image URLs to a local /img-proxy/... path with width, quality, and format=auto parameters the optimizer understands,
  • a route handler that proxies those paths back to the media host and streams the image, so the optimizer can transform it on the way to the browser.

Image URLs that do not belong to a configured media host (for example CMS assets served from the CMS's own CDN) are passed through unchanged.

Prerequisites

The Alokai Image Optimizer needs to be turned on on your Alokai instance. Contact the Alokai Customer Support team if you want to use this feature.

This feature is available for both Next.js and Nuxt storefronts. Next.js requires @vue-storefront/next 8.0.0 or higher (which introduces createImageOptimizer); Nuxt requires @vue-storefront/nuxt 11.0.0 or higher (which adds the image optimizer to the module).

How it works

  1. A component renders an image whose src is a media-host URL, for example https://media.example.com/products/shoe.jpg at width={640}.
  2. The custom loader rewrites the URL to /img-proxy/ct/products/shoe.jpg?width=640&quality=85&format=auto.
  3. The browser requests that path from your storefront domain, so the request travels through the Alokai edge.
  4. A proxy route on your storefront (app/img-proxy/[host]/[...path]/route.ts on Next.js, a /img-proxy/:host/** server route on Nuxt) fetches the original image from the media host and streams it back.
  5. The image optimizer applies the width, quality, and format transformations to the response on its way back to the browser.

Setup

In every example below, the host key (for example ct or commerce) becomes the /img-proxy/{key}/... URL segment and derives the media host environment variable by convention. There is no need to set that variable locally - if it is unset, the storefront keeps working: images are served unoptimized and a warning is logged.

1. Set the media host environment variable

The host key derives the variable name as NEXT_PUBLIC_{KEY}_MEDIA_HOST. Set it (for example NEXT_PUBLIC_CT_MEDIA_HOST=https://images.cdn.europe-west1.gcp.commercetools.com) in the Alokai Console environment settings - see the Alokai Console documentation on Environment variables.

2. Create the config file

This is the single place where the image optimizer is configured. The default export is the custom image loader, so the file doubles as the loaderFile.

// apps/storefront-unified-nextjs/config/image-optimizer.ts

import { createImageOptimizer } from '@vue-storefront/next';

export const { GET, loader } = createImageOptimizer({
  hosts: { ct: {} },
});

export default loader;

3. Register the custom image loader

Point the Next.js images config directly at the config file.

// apps/storefront-unified-nextjs/next.config.mjs

const nextConfig = {
  // ...
  images: {     loader: 'custom',
    loaderFile: './config/image-optimizer.ts',
    remotePatterns: [{ hostname: '*', protocol: 'https' }],
  },
};

If your storefront already has an images config (for example in config/image-loaders/default.config.mjs), update its loaderFile to point at ./config/image-optimizer.ts instead of adding a new images entry.

4. Add the proxy route

// apps/storefront-unified-nextjs/app/img-proxy/[host]/[...path]/route.ts

export { GET } from '@/config/image-optimizer';

The folder path matters: the loader emits URLs under /img-proxy/{host}/..., so the route must live at app/img-proxy/[host]/[...path]/route.ts.

Multiple media hosts

You can handle images from several hosts with a single configuration. Each key becomes a URL segment (/img-proxy/{key}/...) and derives its own environment variable, so name each key after the host it should read:

export const { GET, loader } = createImageOptimizer({
  hosts: {
    commerce: {}, // reads NEXT_PUBLIC_COMMERCE_MEDIA_HOST
    cms: {}, // reads NEXT_PUBLIC_CMS_MEDIA_HOST
  },
});

The loader matches the image src against the hosts in configuration order; the first match wins. A host that already has its own image CDN can be served by it instead of being proxied (see Hosts with their own image CDN).

SAP Commerce Cloud

SAP CC resolves media through a ?context= query parameter, which the image CDN strips before fetching the origin. The sapcc variant handles this by encoding the context as a path segment and reconstructing the query parameter in the route handler:

export const { GET, loader } = createImageOptimizer({
  hosts: {
    sapcc: { variant: 'sapcc' },
  },
});

Cache-Control

Successful responses are served with public, max-age=3600, s-maxage=86400 by default. If your platform versions media URLs per upload (commercetools does), the response can be treated as immutable - override it per host:

export const { GET, loader } = createImageOptimizer({
  hosts: {
    ct: { cacheControl: 'public, max-age=31536000, immutable' },
  },
});

Error responses always get no-store, no-cache, must-revalidate so failures are never cached.

The Cache-Control header returned by the media host is intentionally ignored - the configured (or default) value always wins. Media backends commonly send no-cache or private for assets that are perfectly cacheable, which would silently disable edge caching and with it the optimization itself. Cacheability of proxied images is decided by your per-host configuration, not by the upstream response.

Hosts with their own image CDN

If a host already provides an image CDN - for example Contentful's Images API - you usually want to use that CDN directly for its assets while the Alokai Image Optimizer keeps handling your commerce media. The two run side by side: commerce images flow through /img-proxy/..., while CMS images go straight to the CMS's own CDN.

Pass a per-host loader with the standard Next.js image loader signature; matching images are rewritten to the host's own CDN and never proxied:

export const { GET, loader } = createImageOptimizer({
  hosts: {
    contentful: {
      loader: ({ quality, src, width }) => `${src}?w=${width}&q=${quality ?? 75}&fm=webp`,
    }, // derives NEXT_PUBLIC_CONTENTFUL_MEDIA_HOST
    ct: {},
  },
});

Custom URL schemes

Next.js only. On Nuxt, handle an unsupported scheme with a custom @nuxt/image provider.

For platforms not covered by the built-in variants, each host accepts two low-level hooks instead of variant:

  • encodePath(pathname, searchParams) - builds the path emitted after /img-proxy/{key} on the loader side; return undefined to skip optimization for that URL,
  • buildUpstreamUrl(mediaHost, segments) - rebuilds the upstream URL from the route segments on the server side.

Two things to keep in mind when writing hooks:

  • The image CDN strips query strings before fetching the origin, so the default variant discards the query string of the original image URL. If your host needs query parameters (signed URLs, resolution contexts), encode them into the path in encodePath and reconstruct them in buildUpstreamUrl - that is exactly what the sapcc variant does with ?context=.
  • buildUpstreamUrl receives decoded path segments. Re-encode each one (segments.map(encodeURIComponent).join('/')) when building the upstream URL, otherwise a crafted segment like a%2F..%2Fadmin can traverse paths or inject query parameters on the media host.

The proxied URL must also contain a dot (a file extension) - the storefront middleware matcher skips only dotted paths, so an extensionless path gets locale-prefixed by next-intl and never reaches the route handler. The sapcc variant guarantees this with a synthetic filename; if your platform serves extensionless image URLs, append one in encodePath (and strip it in buildUpstreamUrl if the host does not ignore it).

Validating the setup

1

Run your project and open a page with product images.

2

Open the network tab of your browser's dev tools and filter by img-proxy.

3

Image requests should hit /img-proxy/{host}/... paths with width, quality, and format=auto query parameters, and responses should include the configured Cache-Control header. On an instance with the image optimizer enabled, the response Content-Type reflects the negotiated format (for example image/webp).