Skip to main content
Version: 3.40.0

Run Next.js Image Optimizer as a Dedicated Service

In this guide, you will configure Platformatic Next to run in Image Optimizer mode as a standalone service.

Instead of running a full Next.js application in the same process, you expose only the /_next/image endpoint and delegate image optimization to a dedicated component.

Architecture diagram

Why this architecture

Using a dedicated optimizer service behind Gateway has practical advantages:

  • Scalability: image transformations are CPU-intensive and can be scaled independently from frontend rendering.
  • Operational isolation: spikes on image traffic do not starve your frontend workers.
  • Centralized behavior: width/quality validation, retry policy, timeout, and cache/storage settings are configured once.
  • Flexible storage backends: in-memory for local dev, filesystem for simple deployments, Redis/Valkey for multi-instance environments.
  • Runtime-native service discovery: relative image paths are fetched from a sibling application (for example http://frontend.plt.local) without hardcoding external URLs.

Prerequisites

Before starting, make sure you have:

  • npm available on your machine
  • wattpm available through npx wattpm (or installed globally)

What you will build

You will create a runtime with three applications:

  1. gateway (entrypoint)
    • runs @platformatic/gateway
    • routes only GET /_next/image to optimizer using proxy.routes; everything else goes to frontend
  2. frontend
    • runs your regular Next.js app (pages, APIs, and static assets)
    • serves static files (for example public/platformatic.png)
    • provides original assets for relative image URLs
  3. optimizer
    • runs @platformatic/next in Image Optimizer mode
    • exposes only /_next/image

At the end, you will test:

  • optimization of a relative URL (/platformatic.png) resolved through local service discovery
  • optimization of an absolute URL (https://...) fetched directly

1) Create a runtime with three applications

Create this structure:

my-runtime/
watt.json
web/
gateway/
watt.json
package.json
frontend/
watt.json
package.json
next.config.js
public/
platformatic.png
src/
app/
page.jsx
optimizer/
watt.json
package.json
next.config.js

In this setup:

  • gateway is the external-facing entrypoint
  • frontend is where relative assets are fetched from
  • optimizer handles only /_next/image

2) Configure the runtime

Create my-runtime/watt.json:

{
"$schema": "https://schemas.platformatic.dev/@platformatic/runtime/3.0.0.json",
"entrypoint": "gateway",
"applications": [
{
"id": "gateway",
"path": "./web/gateway"
},
{
"id": "frontend",
"path": "./web/frontend"
},
{
"id": "optimizer",
"path": "./web/optimizer"
}
]
}

Why this configuration?

  • entrypoint: "gateway" means incoming runtime traffic goes first to Gateway.
  • Gateway routes by method + path (via proxy.methods and proxy.routes).
  • frontend and optimizer are in the same runtime, so service discovery works automatically.
  • frontend is reachable from optimizer as http://frontend.plt.local.

3) Configure Gateway with route-based proxying

Create my-runtime/web/gateway/watt.json:

{
"$schema": "https://schemas.platformatic.dev/@platformatic/gateway/3.0.0.json",
"gateway": {
"applications": [
{
"id": "frontend",
"proxy": {
"prefix": "/",
"routes": ["/*"]
}
},
{
"id": "optimizer",
"proxy": {
"prefix": "/",
"routes": ["/_next/image"],
"methods": ["GET"]
}
}
]
}
}

Why use proxy.routes?

  • /_next/image requests are routed to optimizer only.
  • all other requests are routed to frontend.
  • this keeps image optimization isolated while the frontend app serves regular pages/assets.

4) Configure the optimizer application

Create my-runtime/web/optimizer/watt.json:

{
"$schema": "https://schemas.platformatic.dev/@platformatic/next/3.38.1.json",
"next": {
"imageOptimizer": {
"enabled": true,
"fallback": "frontend",
"timeout": 30000,
"maxAttempts": 3
}
}
}

What these options mean

  • enabled: true enables dedicated Image Optimizer mode.
  • fallback: "frontend" resolves relative URLs against http://frontend.plt.local.
  • timeout: 30000 sets a 30-second timeout budget for fetch/optimization jobs.
  • maxAttempts: 3 retries failed jobs up to 3 times.

5) Configure the frontend application

Create my-runtime/web/frontend/watt.json:

{
"$schema": "https://schemas.platformatic.dev/@platformatic/next/3.38.1.json"
}

Then place a test image here:

  • my-runtime/web/frontend/public/platformatic.png

This makes /platformatic.png available from the frontend application.

Keep frontend as a standard Next.js app

frontend should be your full frontend application, not a stripped-down placeholder. Keep the same pages, route handlers, API routes, middleware, and static assets you would normally ship.

Minimal example (my-runtime/web/frontend/src/app/page.jsx):

import Image from 'next/image'

export default function Page () {
return (
<main>
<h1>Frontend app served by frontend</h1>
<Image src="/platformatic.png" alt="Platformatic logo" width={512} height={512} />
</main>
)
}

The optimizer service remains dedicated to /_next/image; all other application behavior continues to live in frontend.

6) Install dependencies

Define dependencies in each application folder.

6.1) Gateway package.json

Create or update my-runtime/web/gateway/package.json:

{
"name": "gateway",
"private": true,
"dependencies": {
"@platformatic/gateway": "^3.0.0"
}
}

6.2) Frontend package.json

Create or update my-runtime/web/frontend/package.json:

{
"name": "frontend",
"private": true,
"dependencies": {
"@platformatic/next": "^3.38.1",
"next": "^15.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
}

6.3) Optimizer package.json

Create or update my-runtime/web/optimizer/package.json:

{
"name": "optimizer",
"private": true,
"dependencies": {
"@platformatic/next": "^3.38.1",
"next": "^15.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
}

From my-runtime/, run:

npx wattpm-utils install

7) Start the runtime

From my-runtime/, run:

npx wattpm start

By default, Runtime listens on http://127.0.0.1:3042 unless you configure a different host/port.

8) Test the optimizer endpoint

Test A: optimize an internal image (relative URL)

curl -i "http://127.0.0.1:3042/_next/image?url=/platformatic.png&w=1024&q=75"

Request flow:

  1. Gateway receives the request.
  2. proxy.routes matches /_next/image and forwards to optimizer.
  3. optimizer detects url=/platformatic.png is relative.
  4. Source is resolved to http://frontend.plt.local/platformatic.png.
  5. The image is fetched and optimized.
  6. Gateway returns the optimized bytes.

Test B: optimize an external image (absolute URL)

curl -i "http://127.0.0.1:3042/_next/image?url=https%3A%2F%2Fexample.com%2Fimage.png&w=1024&q=75"

This request is forwarded by Gateway to optimizer, then fetched directly from the absolute URL.

Expected result

For valid requests, you should receive:

  • HTTP/1.1 200 OK
  • an image content-type (for example image/png)

9) Production topology: Gateway in front of your main Next.js app + optimizer

A common production setup is:

  • one frontend Next.js application for pages and APIs
  • one dedicated optimizer application
  • one gateway entrypoint routing traffic between them

Example Gateway routing:

  • GET /_next/image -> optimizer
  • /* -> frontend

web/gateway/watt.json:

{
"$schema": "https://schemas.platformatic.dev/@platformatic/gateway/3.0.0.json",
"gateway": {
"applications": [
{
"id": "frontend",
"proxy": {
"prefix": "/",
"routes": ["/*"]
}
},
{
"id": "optimizer",
"proxy": {
"prefix": "/",
"routes": ["/_next/image"],
"methods": ["GET"]
}
}
]
}
}

web/optimizer/watt.json:

{
"$schema": "https://schemas.platformatic.dev/@platformatic/next/3.38.1.json",
"next": {
"imageOptimizer": {
"enabled": true,
"fallback": "frontend"
}
}
}

In this model, relative image URLs are fetched from frontend.plt.local, while optimization workloads are isolated in the dedicated optimizer application.


10) Kubernetes ingress example (NGINX Ingress Controller)

If you expose frontend and optimizer as separate Kubernetes Services, you can route image requests at the ingress layer:

  • /_next/image -> optimizer Service
  • all other paths -> frontend Service

Architecture diagram

Example Ingress resource:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: next-image-optimizer
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: app.example.com
http:
paths:
- path: /_next/image
pathType: Exact
backend:
service:
name: optimizer
port:
number: 3044
- path: /
pathType: Prefix
backend:
service:
name: frontend
port:
number: 3043

Notes:

  • pathType: Exact on /_next/image prevents broad prefix matching from capturing the route.
  • Service names/ports must match your Kubernetes Service definitions.
  • If you expose only the Platformatic Runtime gateway service externally, keep ingress pointing to gateway and let proxy.routes handle /_next/image internally.

Storage options

Image optimization jobs run through a queue. You can control queue persistence with next.imageOptimizer.storage.

If you do not set storage, the default is in-memory storage.

1) Memory storage (default)

Best for local development or simple deployments where persistence across restarts is not required.

{
"next": {
"imageOptimizer": {
"enabled": true,
"fallback": "frontend",
"storage": {
"type": "memory"
}
}
}
}

2) Filesystem storage

Useful when you want persistence on local disk (single node/container with writable volume).

{
"next": {
"imageOptimizer": {
"enabled": true,
"fallback": "frontend",
"storage": {
"type": "filesystem",
"path": "./.next/cache/image-optimizer"
}
}
}
}

3) Redis/Valkey storage

Recommended for distributed/multi-instance deployments.

{
"next": {
"imageOptimizer": {
"enabled": true,
"fallback": "frontend",
"storage": {
"type": "valkey",
"url": "redis://localhost:6379",
"prefix": "next-image:",
"db": 0
}
}
}
}

You can also use "type": "redis".

Next steps

  • Put this optimizer pattern in front of one or more frontend applications.
  • Configure Redis/Valkey storage for production.
  • Tune timeout and maxAttempts based on traffic profile and upstream reliability.

For full option details, see the Next.js Image Optimizer reference.