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 withwidth,quality, andformat=autoparameters 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
- A component renders an image whose
srcis a media-host URL, for examplehttps://media.example.com/products/shoe.jpgatwidth={640}. - The custom loader rewrites the URL to
/img-proxy/ct/products/shoe.jpg?width=640&quality=85&format=auto. - The browser requests that path from your storefront domain, so the request travels through the Alokai edge.
- A proxy route on your storefront (
app/img-proxy/[host]/[...path]/route.tson Next.js, a/img-proxy/:host/**server route on Nuxt) fetches the original image from the media host and streams it back. - The image optimizer applies the
width,quality, andformattransformations 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 variables 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.
5. Enable the optimizer
Set NEXT_PUBLIC_IMAGE_OPTIMIZER_ENABLED=true in the Alokai Console environment variables settings.
1. Set the media host environment variable
The host key derives the variable name as NUXT_PUBLIC_{KEY}_MEDIA_HOST. Set it (for example NUXT_PUBLIC_COMMERCE_MEDIA_HOST=https://images.cdn.europe-west1.gcp.commercetools.com) in the Alokai Console environment variables settings - see the Alokai Console documentation on Environment variables.
2. Configure the module
Add the hosts under alokai.imageOptimizer.hosts in nuxt.config.ts. The module wires up the @nuxt/image provider and the proxy server route for you - there is no config file, loader file, or route file to author by hand.
// apps/storefront-unified-nuxt/nuxt.config.ts
export default defineNuxtConfig({
alokai: {
imageOptimizer: {
hosts: {
commerce: {}, // reads NUXT_PUBLIC_COMMERCE_MEDIA_HOST
},
},
},
modules: [
// Must precede @nuxt/image: the module registers the Alokai provider
// before @nuxt/image resolves its providers.
'@vue-storefront/nuxt',
'@nuxt/image',
],
});@vue-storefront/nuxt must come before @nuxt/image in modules, because it registers the alokai image provider (and makes it the default) before @nuxt/image resolves its providers. The optimizer only activates when at least one host is configured.
The media host value is read from Nuxt's public runtimeConfig, which Nuxt overrides at runtime from NUXT_PUBLIC_{KEY}_MEDIA_HOST - so it stays overridable per deployment. Host keys must be lowercase letters, digits, and hyphens and contain at least one letter (an invalid key throws at module init).
3. Enable the optimizer
Set NUXT_PUBLIC_IMAGE_OPTIMIZER_ENABLED=true in the Alokai Console environment variables settings.
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
},
});// nuxt.config.ts
alokai: {
imageOptimizer: {
hosts: {
commerce: {}, // reads NUXT_PUBLIC_COMMERCE_MEDIA_HOST
cms: {}, // reads NUXT_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' },
},
});// nuxt.config.ts
alokai: {
imageOptimizer: {
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' },
},
});// nuxt.config.ts
alokai: {
imageOptimizer: {
hosts: {
commerce: { 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 }) => {
const url = new URL(src);
url.searchParams.set('w', String(width));
url.searchParams.set('q', String(quality ?? 75));
url.searchParams.set('fm', 'avif');
return url.toString();
},
}, // derives NEXT_PUBLIC_CONTENTFUL_MEDIA_HOST
ct: {},
},
});Build the URL with URL/searchParams rather than a template string - src may already carry query parameters, and appending ?... blindly would produce an invalid URL.
On Nuxt, the Alokai module only sets alokai as the default provider, so a host with its own CDN is served by a second @nuxt/image provider rather than by the optimizer. Keep commerce media on the default alokai provider, and configure the CMS host as a native provider - @nuxt/image ships a built-in Contentful provider:
// nuxt.config.ts
export default defineNuxtConfig({
alokai: {
imageOptimizer: {
hosts: {
commerce: {}, // commerce media -> Alokai Image Optimizer
},
},
},
image: {
contentful: {
// Optional - this is also the provider's default baseURL.
baseURL: 'https://images.ctfassets.net',
},
},
modules: [
'@vue-storefront/nuxt', // still must precede @nuxt/image
'@nuxt/image',
],
});@nuxt/image picks the provider per image rather than by URL, so render CMS images with the contentful provider explicitly. Contentful's Images API has no format=auto, so request an explicit modern format:
<NuxtImg provider="contentful" format="webp" :src="image.url" :width="300" :height="300" />To keep that choice in one place, wrap it in a small component your CMS components reuse instead of repeating provider="contentful" on every image:
<!-- components/cms/CmsImg.vue -->
<template>
<NuxtImg provider="contentful" format="webp" v-bind="$attrs" />
</template>
<script lang="ts" setup>
defineOptions({ inheritAttrs: false });
</script>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; returnundefinedto 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
encodePathand reconstruct them inbuildUpstreamUrl- that is exactly what thesapccvariant does with?context=. buildUpstreamUrlreceives decoded path segments. Re-encode each one (segments.map(encodeURIComponent).join('/')) when building the upstream URL, otherwise a crafted segment likea%2F..%2Fadmincan 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
Run your project and open a page with product images.
Open the network tab of your browser's dev tools and filter by img-proxy.
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).