126 lines
4.4 KiB
Markdown
126 lines
4.4 KiB
Markdown
# 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 <Image src="/images/product.jpg" width={640} height={420} alt="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.
|