# Next/image Provider `image-platform` должен работать как custom image provider для `next/image`. ## Next.js contract Next.js custom loader получает только: - `src`; - `width`; - `quality`. Поэтому public image URL должен принимать эти параметры напрямую и сам запускать read-through генерацию при miss. ## Remote source loader config Remote source mode нужен для сценария, где consumer project уже имеет изображение в `public` или внешний image URL и хочет получить `srcset` без предварительной регистрации asset. В Next.js приложении используется `loaderFile`: ```js // next.config.js module.exports = { images: { loader: "custom", loaderFile: "./src/image-platform-loader.js", qualities: [75, 80], }, } ``` Пример loader: ```js "use client" import { createImagePlatformNextLoader } from "@image-platform/client" export default createImagePlatformNextLoader({ baseUrl: process.env.NEXT_PUBLIC_IMAGE_PLATFORM_URL, preset: "card", project: process.env.NEXT_PUBLIC_IMAGE_PLATFORM_PROJECT, sourceBaseUrl: process.env.NEXT_PUBLIC_SITE_ORIGIN, }) ``` Пример использования: ```tsx import Image from "next/image" export function ProductCard() { return Product } ``` Если `src` относительный, `sourceBaseUrl` превращает его в абсолютный source URL, например `https://site.example.com/images/product.jpg`. Если `src` уже абсолютный, он передаётся как есть. ## Public URL Remote source URL: ```text GET /p/{project}/remote/{preset}?src={sourceUrl}&w={width}&q={quality}&f=auto ``` Пример: ```text https://img.example.com/p/acme/remote/card?src=https%3A%2F%2Fsite.example.com%2Fimages%2Fproduct.jpg&w=640&q=80&f=auto ``` Managed asset URL: ```text GET /images/{assetId}/v{version}/{preset}?w={width}&q={quality}&f=auto ``` Route реализован в Fastify Gateway. Для `card` ширина должна входить в static preset allowlist: `320`, `640`, `960`. Пример: ```text https://img.example.com/images/asset_123/v4/card?w=640&q=80&f=auto ``` Для managed asset `src` не должен быть source URL. Это должен быть versioned platform identifier, например `asset_123/v4/card`. Source URL хранится в PostgreSQL и не раскрывается в public image URL. `v{version}` меняется при обновлении source image. Старые URL можно кэшировать как immutable без purge. Для remote source `src` является исходным URL. Этот режим не immutable по умолчанию: Gateway отдаёт `GATEWAY_REMOTE_CACHE_CONTROL`, потому что источник может быть mutable. ## Format auto `f=auto` выбирает output format по `Accept` header: 1. `image/avif`, если клиент поддерживает AVIF и preset разрешает AVIF. 2. `image/webp`, если клиент поддерживает WebP и preset разрешает WebP. 3. `image/jpeg` или original fallback. Для auto format обязательны headers: ```http Vary: Accept Cache-Control: public, max-age=31536000, immutable Content-Type: image/avif | image/webp | image/jpeg ``` CDN и Gateway L1 cache должны учитывать `Vary: Accept`, иначе можно отдать AVIF клиенту без AVIF support. ## Read-through behavior ```text client -> CDN -> Fastify gateway -> L1 memory -> Backend -> RabbitMQ -> Worker -> imgproxy -> S3 ``` Поведение: - CDN HIT: backend не вызывается. - Gateway L1 HIT: backend не вызывается. - Gateway L1 MISS: Gateway вызывает Backend internal ensure endpoint. - S3 HIT: Backend отдаёт bytes Gateway, Gateway кладёт result в L1. - S3 MISS: Backend ставит RabbitMQ job, Worker генерирует variant через external imgproxy, сохраняет в S3, обновляет PostgreSQL, Backend возвращает bytes Gateway. Так достигается Cloudinary-like поведение: первый запрос создаёт derived asset, следующие запросы отдаются из cache/storage.