Relevant source files
The following files were used as context for generating this wiki page:
- packages/next/src/server/image-optimizer.ts
- packages/next/src/server/image-optimizer/transform.ts
- packages/next/src/client/legacy/image.tsx
- packages/next/src/server/og/cache-image-response.ts
- packages/next/src/server/next-server.ts
- packages/next/src/build/webpack/loaders/next-image-loader/index.ts
- packages/next/src/shared/lib/get-img-props.ts
- packages/next/image-optimizer-transform.js
- packages/next/src/client/image-component.tsx
- packages/next/src/shared/lib/image-loader.ts
- packages/next/src/server/image-optimizer/extract-etag.ts
- packages/next/src/build/webpack/loaders/next-image-loader/blur.ts
- packages/next/src/build/webpack/loaders/next-metadata-image-loader.ts
- packages/next/image-optimizer-transform.d.ts
- packages/next/src/shared/lib/image-config.ts
- packages/next/src/server/image-optimizer/image-type.ts
- packages/next/src/server/image-optimizer/get-image-size.ts
- packages/next/image.js
- packages/next/src/server/og/image-response.ts
- packages/next/legacy/image.js
- packages/next/src/shared/lib/image-blur-svg.ts
- packages/next/src/api/image.ts
Overview
Next.js Image Optimization provides an integrated pipeline for resizing, reformatting, and caching raster and vector graphics to maximize web performance and core web vitals. The subsystem spans client-side attribute generation via [[Metadata Generation]] and component props, build-time dimension inspection and placeholder creation, server-side request routing and security enforcement across [[Server Architecture]], and robust on-demand image transformation backed by Sharp with content negotiation for modern formats like AVIF and WebP.
Sources: packages/next/src/server/image-optimizer.ts:47-57, packages/next/src/server/next-server.ts:995-1002, packages/next/src/client/image-component.tsx:14-16, packages/next/src/build/webpack/loaders/next-image-loader/index.ts:42-62, packages/next/src/server/image-optimizer/transform.ts:181-195
Client Attribute Generation and Loaders
Public API and Attribute Generation
The public client image component exported from [[Metadata Generation]] (packages/next/src/api/image.ts) builds upon getImgProps to transform user-supplied properties into attributes for the underlying <img> element. During attribute generation, generateImgAttrs inspects the image configuration and dimensions to compute responsive srcSet strings and default sizes fallback values.
const { widths, kind } = getWidths(config, width, sizes)
const last = widths.length - 1
return {
sizes: !sizes && kind === 'w' ? '100vw' : sizes,
srcSet: widths
.map(
(w, i) =>
`${loader({ config, src, quality, width: w })} ${
kind === 'w' ? w : i + 1
}${kind}`
)
.join(', '),
// It's intended to keep `src` the last attribute because React updates
// attributes in order. If we keep `src` the first one, Safari will
// immediately start to fetch `src`, before `sizes` and `srcSet` are even
// updated by React. That causes multiple unnecessary requests if `srcSet`
// and `sizes` are defined.
// This bug cannot be reproduced in Chrome or Firefox.
src: loader({ config, src, quality, width: widths[last] }),
}Sources: packages/next/src/shared/lib/get-img-props.ts:255-276
When the unoptimized flag is enabled, or when asset URLs utilize protocol-relative or data schemes, optimization is bypassed. For local static assets rendered without optimization when a deployment identifier is present, the framework appends the ?dpl= query parameter to ensure proper cache busting, while skipping assets flagged as immutable static resources unless an asset token is available.
if (unoptimized) {
if (src.startsWith('/') && !src.startsWith('//')) {
let deploymentId = getDeploymentId()
if (src.includes('/_next/static/immutable') && !getAssetToken()) {
// immutable static asset and supported by platform, don't add `?dpl=`
deploymentId = undefined
} else if (deploymentId) {
// We unfortunately can't easily use `new URL()` here, because it normalizes the URL which causes
// double-encoding with the `encodeURIComponent(src)` below
const qIndex = src.indexOf('?')
if (qIndex !== -1) {
const params = new URLSearchParams(src.slice(qIndex + 1))
const srcDpl = params.get('dpl')
if (!srcDpl) {
// src is missing the dpl parameter, but we have a deploymentId, so add it to the src URL
params.append('dpl', deploymentId)
src = src.slice(0, qIndex) + '?' + params.toString()
}
} else {
// src is missing the dpl parameter, but we have a deploymentId, so add it to the src URL
src = src + `?dpl=${deploymentId}`
}
}
}
return { src, srcSet: undefined, sizes: undefined }
}Sources: packages/next/src/shared/lib/get-img-props.ts:228-253
Warning
React updates element attributes in the exact order they are spread or declared. To prevent Safari from prematurely fetching the primary src before responsive sizes and srcSet descriptors are populated by React, the src attribute is intentionally placed as the final property on the rendered <img> element.
Sources: packages/next/src/client/image-component.tsx:283-291, packages/next/src/shared/lib/get-img-props.ts:269-276
Loader URL Generation and Configuration
Image loaders translate source paths, requested widths, and qualities into concrete URLs. The built-in default loader validates presence parameters, manages deployment ID query tokens, and formats the optimization request path.
| Valid Loader Identifier | Description |
|---|---|
default | Uses the built-in Next.js image optimization endpoint (/_next/image). |
imgix | Generates URLs targeting the Imgix cloud image service. |
cloudinary | Generates URLs targeting Cloudinary. |
akamai | Generates URLs targeting Akamai image manager. |
custom | Delegates URL construction entirely to a user-defined custom loader function. |
Sources: packages/next/src/shared/lib/image-config.ts:1-7, packages/next/src/shared/lib/image-loader.ts:5-128
const q = findClosestQuality(quality, config)
return `${config.path}?url=${encodeURIComponent(src)}&w=${width}&q=${q}${
src.startsWith('/') && deploymentId ? `&dpl=${deploymentId}` : ''
}`
}Sources: packages/next/src/shared/lib/image-loader.ts:123-128
Caution
Protocol-relative image sources starting with // are strictly rejected by the default loader in development mode and must be explicitly converted to absolute URLs utilizing either http:// or https://.
Sources: packages/next/src/shared/lib/image-loader.ts:67-72
Build Time Dimensions and Placeholders
Static Image Imports and Dimension Inspection
When static assets are imported in source code, the next-image-loader processes the binary buffer via getImageSize to extract intrinsic width and height dimensions. Filename hashes are interpolated into /static/media/[name].[hash:8].[ext], with jpg extensions normalized to jpeg.
const interpolatedName = loaderUtils.interpolateName(
this,
'/static/media/[name].[hash:8].[ext]',
opts
)
const outputPath = assetPrefix + '/_next' + interpolatedName
let extension = loaderUtils.interpolateName(this, '[ext]', opts)
if (extension === 'jpg') {
extension = 'jpeg'
}
const imageSizeSpan = imageLoaderSpan.traceChild('image-size-calculation')
const imageSize = await imageSizeSpan.traceAsyncFn(() =>
getImageSize(content).catch((err) => err)
)
if (imageSize instanceof Error) {
const err = imageSize
err.name = 'InvalidImageFormatError'
throw err
}Sources: packages/next/src/build/webpack/loaders/next-image-loader/index.ts:31-51
If the underlying buffer cannot be decoded, getImageSize captures the parsing failure and assigns the InvalidImageFormatError name to the thrown error instance.
export async function getImageSize(buffer: Buffer): Promise<{
width?: number
height?: number
}> {
const { width, height } = imageSizeOf(buffer)
return { width, height }Sources: packages/next/src/server/image-optimizer/get-image-size.ts:3-8, packages/next/src/build/webpack/loaders/next-image-loader/index.ts:43-51
Warning
If getImageSize encounters an unparseable or corrupted payload, it throws an error that the loader explicitly catches and renames to InvalidImageFormatError prior to failing the webpack compilation step.
Sources: packages/next/src/build/webpack/loaders/next-image-loader/index.ts:43-51
Blur Placeholder Generation and SVG Formatting
The blur generation pipeline evaluates whether asset extensions and animation frames permit placeholder creation using fixed dimension and quality constraints.
| Constant Name | Value | Purpose |
|---|---|---|
BLUR_IMG_SIZE | 8 | The target pixel size for the largest dimension of the resized blur buffer. |
BLUR_QUALITY | 70 | The compression quality setting passed to the image optimizer. |
VALID_BLUR_EXT | ['jpeg', 'png', 'webp', 'avif'] | The array of allowed image formats eligible for blur generation. |
Sources: packages/next/src/build/webpack/loaders/next-image-loader/blur.ts:4-6
if (VALID_BLUR_EXT.includes(extension) && !isAnimated(content)) {
// Shrink the image's largest dimension
if (imageSize.width >= imageSize.height) {
blurWidth = BLUR_IMG_SIZE
blurHeight = Math.max(
Math.round((imageSize.height / imageSize.width) * BLUR_IMG_SIZE),
1
)
} else {
blurWidth = Math.max(
Math.round((imageSize.width / imageSize.height) * BLUR_IMG_SIZE),
1
)
blurHeight = BLUR_IMG_SIZE
}
if (isDev) {
// During `next dev`, we don't want to generate blur placeholders with webpack
// because it can delay starting the dev server. Instead, we inline a
// special url to lazily generate the blur placeholder at request time.
const prefix = 'http://localhost'
const url = new URL(`${basePath || ''}/_next/image`, prefix)
url.searchParams.set('url', outputPath)
url.searchParams.set('w', String(blurWidth))
url.searchParams.set('q', String(BLUR_QUALITY))
blurDataURL = url.href.slice(prefix.length)
} else {
const resizeImageSpan = tracing('image-resize')
const resizedImage = await resizeImageSpan.traceAsyncFn(() =>
optimizeImage({
buffer: content,
width: blurWidth,
height: blurHeight,
contentType: `image/${extension}`,
quality: BLUR_QUALITY,
})
)
const blurDataURLSpan = tracing('image-base64-tostring')
blurDataURL = blurDataURLSpan.traceFn(
() =>
`data:image/${extension};base64,${resizedImage.toString('base64')}`
)
}
}Sources: packages/next/src/build/webpack/loaders/next-image-loader/blur.ts:40-83
Note
During development mode (isDev equals true), getBlurImage bypasses heavy webpack-side image compression to prevent startup delays, instead generating a lazy /_next/image URL endpoint query.
Sources: packages/next/src/build/webpack/loaders/next-image-loader/blur.ts:56-65
Shared SVG placeholder utilities scale the resulting base64 data URL using an SVG filter definition containing Gaussian blur (stdDeviation='20') and color matrices.
export function getImageBlurSvg({
widthInt,
heightInt,
blurWidth,
blurHeight,
blurDataURL,
objectFit,
}: {
widthInt?: number
heightInt?: number
blurWidth?: number
blurHeight?: number
blurDataURL: string
objectFit?: string
}): string {
const std = 20
const svgWidth = blurWidth ? blurWidth * 40 : widthInt
const svgHeight = blurHeight ? blurHeight * 40 : heightInt
const viewBox =
svgWidth && svgHeight ? `viewBox='0 0 ${svgWidth} ${svgHeight}'` : ''
const preserveAspectRatio = viewBox
? 'none'
: objectFit === 'contain'
? 'xMidYMid'
: objectFit === 'cover'
? 'xMidYMid slice'
: 'none'
return `%3Csvg xmlns='http://www.w3.org/2000/svg' ${viewBox}%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='${std}'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='${std}'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='${preserveAspectRatio}' style='filter: url(%23b);' href='${blurDataURL}'/%3E%3C/svg%3E`Sources: packages/next/src/shared/lib/image-blur-svg.ts:4-33
Request Dispatch and Security Checks
Parameter Validation and Configuration Handling
Incoming requests targeting the image optimization server route are intercepted by the ImageOptimizerCache.validateParams method. This method extracts query string parameters including url, w (width), and q (quality), validating them against the application's runtime configuration (NextConfigRuntime).
Sources: packages/next/src/server/image-optimizer.ts:178-185
Default configuration properties defined in imageConfigDefault provide fallback constraints for sizes, domains, minimum cache time-to-live, and remote patterns.
| Config Property | Default Value | Purpose |
|---|---|---|
deviceSizes | [640, 750, 828, 1080, 1200, 1920, 2048, 3840] | Width breakpoints used for responsive layouts across device viewports. |
imageSizes | [32, 48, 64, 96, 128, 256, 384] | Smaller width sizes used for icons and inline images. |
path | /_next/image | The base URL path prefix for the image optimization endpoint. |
loader | 'default' | The built-in image loader identifier (VALID_LOADERS). |
minimumCacheTTL | 14400 | The minimum cache time-to-live in seconds (4 hours). |
formats | ['image/webp'] | Accepted output image formats for negotiation. |
maximumRedirects | 3 | Maximum number of allowed upstream redirects when fetching remote images. |
maximumResponseBody | 50_000_000 | Maximum permitted upstream response size in bytes (50 MB). |
dangerouslyAllowLocalIP | false | Permits fetching from private or local IP addresses when enabled. |
dangerouslyAllowSVG | false | Permits optimization and serving of SVG images. |
contentSecurityPolicy | script-src 'none'; frame-src 'none'; sandbox; | CSP header injected when serving SVG files. |
contentDispositionType | 'attachment' | Controls the Content-Disposition header disposition value. |
remotePatterns | [] | Array of allowed remote source URL patterns. |
unoptimized | false | Disables image optimization globally when set to true. |
customCacheHandler | false | Routes image caching to a custom cacheHandler implementation. |
Sources: packages/next/src/shared/lib/image-config.ts:237-261
Caution
Setting dangerouslyAllowLocalIP to true bypasses internal Server-Side Request Forgery (SSRF) checks against private IP ranges, allowing the image optimizer to fetch resources from local network addresses.
Sources: packages/next/src/shared/lib/image-config.ts:115-116, packages/next/src/server/image-optimizer.ts:29
SSRF Prevention and Remote Pattern Matching
To prevent Server-Side Request Forgery vulnerabilities, remote image URLs are evaluated against configured remotePatterns and legacy domains lists via hasRemoteMatch. Local paths are verified using hasLocalMatch.
Sources: packages/next/src/server/image-optimizer.ts:14-15
export type RemotePattern = {
/**
* Must be `http` or `https`.
*/
protocol?: 'http' | 'https'
/**
* Can be literal or wildcard.
* Single `*` matches a single subdomain.
* Double `**` matches any number of subdomains.
*/
hostname: string
/**
* Can be literal port such as `8080` or empty string
* meaning no port.
*/
port?: string
/**
* Can be literal or wildcard.
* Single `*` matches a single path segment.
* Double `**` matches any number of path segments.
*/
pathname?: string
/**
* Can be literal query string such as `?v=1` or
* empty string meaning no query string.
*/
search?: string
}Sources: packages/next/src/shared/lib/image-config.ts:36-67
The prepareImageConfig helper ensures options are sorted and structured consistently across server runtime contexts and environment configurations, handling client-side and server-side pattern merging.
export function prepareImageConfig(
envConfig?: ImageConfigForRendering,
contextConfig?: ImageConfigComplete
): PreparedImageConfig {
const envKey = envConfig ?? missingImageConfig
const contextKey = contextConfig ?? missingImageConfig
let preparedByContext = preparedImageConfigs.get(envKey)
if (preparedByContext) {
const cached = preparedByContext.get(contextKey)
if (cached) {
return cached
}
}
const source: ImageConfigForRendering =
envConfig || contextConfig || imageConfigDefault
const deviceSizes = [...source.deviceSizes].sort((a, b) => a - b)
const imageSizes = [...source.imageSizes]
const prepared: PreparedImageConfig = {
deviceSizes,
imageSizes,
allSizes: [...deviceSizes, ...imageSizes].sort((a, b) => a - b),
qualities:
source.qualities === undefined
? undefined
: [...source.qualities].sort((a, b) => a - b),
path: source.path,
loader: source.loader,
dangerouslyAllowSVG: source.dangerouslyAllowSVG,
unoptimized: source.unoptimized,
domains: source.domains,
remotePatterns: source.remotePatterns,
// The inlined browser options supply their own patterns. During SSR the
// context supplies security-sensitive patterns omitted from the inline data.
localPatterns:
typeof window === 'undefined' && contextConfig !== undefined
? contextConfig.localPatterns
: source.localPatterns,
output: source.output,
}
if (!preparedByContext) {
preparedByContext = new WeakMap()
preparedImageConfigs.set(envKey, preparedByContext)
}
preparedByContext.set(contextKey, prepared)
return prepared
}Sources: packages/next/src/shared/lib/image-config.ts:188-235
Image Transformation and Format Negotiation
Content-Type Detection and Bypass Handling
Incoming upstream image buffers are evaluated for content type using detectContentType. If the content type is missing, non-image-prefixed, or contains commas, validation throws an ImageError with status code 400. SVG files trigger an error unless dangerouslyAllowSVG is enabled in configuration. Animated images detected via isAnimated and formats designated as bypass types are returned immediately as raw upstream buffers without resizing or re-encoding.
| Bypass Type | MIME Constant |
|---|---|
| SVG | image/svg+xml |
| ICO | image/x-icon |
| ICNS | image/x-icns |
| BMP | image/bmp |
| JXL | image/jxl |
| HEIC | image/heic |
Sources: packages/next/src/server/image-optimizer/transform.ts:213-260, packages/next/src/server/image-optimizer/transform.ts:31
Warning
Animated variants of image/webp, image/png, and image/gif match ANIMATABLE_TYPES and bypass optimization entirely, returning the original buffer to preserve frame animation data.
Sources: packages/next/src/server/image-optimizer/transform.ts:30, packages/next/src/server/image-optimizer/transform.ts:240-251
Sharp Initialization and Resizing Pipeline
The getSharp function lazily loads the sharp module, blocking all default foreign load operations except for VipsForeignLoadHeif, VipsForeignLoadJpeg, VipsForeignLoadNsgif, VipsForeignLoadPng, VipsForeignLoadSvg, VipsForeignLoadTiff, and VipsForeignLoadWebp. Concurrency is automatically scaled down based on whether execution occurs in development or production.
Sources: packages/next/src/server/image-optimizer/transform.ts:93-135
transformer.avif({
// Scale the quality to try and match webp. This ratio was derived
// from sharp's default 80 (webp) and 50 (avif), and then verified
// using dssim and ssimulacra2 visual quality tests.
quality: Math.max(Math.round(quality * (50 / 80)), 1),
effort: 3,
})
} else if (contentType === WEBP) {
transformer.webp({ quality })
} else if (contentType === PNG) {
transformer.png({ quality })
} else if (contentType === JPEG) {
transformer.jpeg({ quality, mozjpeg })
}Sources: packages/next/src/server/image-optimizer/transform.ts:181-194
Format Negotiation and API Surface
Format negotiation inspects the requested mimeType. If absent, upstream WEBP or AVIF formats are downleveled to JPEG when client request headers do not advertise support. The public transform API contract is exported through next/image-optimizer-transform to run lightweight image transformations within a child process.
Sources: packages/next/src/server/image-optimizer/transform.ts:1-5, packages/next/src/server/image-optimizer/transform.ts:201-272
module.exports = require('./dist/server/image-optimizer/transform')Sources: packages/next/image-optimizer-transform.js:1-1
Cache Storage and ETag Revalidation
Disk LRU and Incremental Cache Integration
Cached optimized images are stored on disk using the ImageOptimizerCache class, which coordinates with a disk LRU cache initialized via getOrInitDiskLRU. When cache directories are scanned, initCacheEntries reads existing keys, file sizes, and expiration timestamps from disk, sorting them chronologically to replay them into the LRU store. Individual cache entries are written to and read from disk directories named after the cache key.
async function initCacheEntries(
cacheDir: string
): Promise<Array<{ key: string; size: number; expireAt: number }>> {
const cacheKeys = await promises.readdir(cacheDir).catch(() => [])
const entries: Array<{ key: string; size: number; expireAt: number }> = []
for (const cacheKey of cacheKeys) {
try {
const { expireAt, buffer } = await readFromCacheDir(cacheDir, cacheKey)
entries.push({
key: cacheKey,
size: buffer.byteLength,
expireAt,
})
} catch {
// Skip entries that can't be read from disk
}
}
// Sort oldest-first so we can replay them chronologically into LRU
return entries.sort((a, b) => a.expireAt - b.expireAt)
}Sources: packages/next/src/server/image-optimizer.ts:69-90
async function writeToCacheDir(
cacheDir: string,
cacheKey: string,
extension: string,
maxAge: number,
expireAt: number,
buffer: Buffer,
etag: string,
upstreamEtag: string
) {
if (buffer.byteLength === 0) {
throw new Error(
'Invariant: cannot write an empty buffer to the image cache'
)
}
const dir = join(/* turbopackIgnore: true */ cacheDir, cacheKey)
const filename = join(
/* turbopackIgnore: true */
dir,
`${maxAge}.${expireAt}.${etag}.${upstreamEtag}.${extension}`
)
await promises.rm(dir, { recursive: true, force: true }).catch(() => {})
await promises.mkdir(dir, { recursive: true })
await promises.writeFile(filename, buffer)
}
async function readFromCacheDir(cacheDir: string, cacheKey: string) {
const dir = join(/* turbopackIgnore: true */ cacheDir, cacheKey)
const files = await promises.readdir(dir)
const file = files[0]
if (!file) {
throw new Error(
`Invariant: cache entry "${cacheKey}" not found in dir "${cacheDir}"`
)
}
const [maxAgeSt, expireAtSt, etag, upstreamEtag, extension] = file.split(
'.',
5
)
const filePath = join(/* turbopackIgnore: true */ dir, file)
const buffer = await promises.readFile(/* turbopackIgnore: true */ filePath)
if (buffer.byteLength === 0) {
throw new Error(`Invariant: image cache entry "${cacheKey}" is empty`)
}
const expireAt = Number(expireAtSt)
const maxAge = Number(maxAgeSt)
return { maxAge, expireAt, etag, upstreamEtag, buffer, extension }
}Sources: packages/next/src/server/image-optimizer.ts:108-158
Warning
Writing an empty buffer (buffer.byteLength === 0) to the image cache throws an immediate invariant error and prevents disk write operations.
Sources: packages/next/src/server/image-optimizer.ts:118-122
ETag Generation and Conditional Request Handling
ETags for optimized images are generated using SHA-256 hashing via getHash, which serializes strings, numbers, and buffers into a base64url-encoded digest. The extractEtag function checks for an upstream ETag; if present, it base64url-encodes the string. If absent, it computes a fresh ETag directly from the image buffer using getImageEtag.
import { createHash } from 'crypto'
export function getHash(items: (string | number | Buffer)[]) {
const hash = createHash('sha256')
for (let item of items) {
if (typeof item === 'number') hash.update(String(item))
else {
hash.update(item)
}
}
// See https://en.wikipedia.org/wiki/Base64#URL_applications
return hash.digest('base64url')
}
export function extractEtag(
etag: string | null | undefined,
imageBuffer: Buffer
) {
if (etag) {
// upstream etag needs to be base64url encoded due to weak etag signature
// as we store this in the cache-entry file name.
return Buffer.from(etag).toString('base64url')
}
return getImageEtag(imageBuffer)
}
export function getImageEtag(image: Buffer) {
return getHash([image])Sources: packages/next/src/server/image-optimizer/extract-etag.ts:1-28
| Function Name | Input Parameters | Output Format |
|---|---|---|
getHash | items: (string | number | Buffer)[] | base64url string |
extractEtag | etag: string | null | undefined, imageBuffer: Buffer | base64url string |
getImageEtag | image: Buffer | base64url string |
Sources: packages/next/src/server/image-optimizer/extract-etag.ts:3-28
Conditional requests are finalized using sendEtagResponse, which verifies cache headers and responds with appropriate status codes when client validation matches stored asset identifiers.
Sources: packages/next/src/server/image-optimizer.ts:26
Related
On This Page
OverviewClient Attribute Generation and LoadersPublic API and Attribute GenerationLoader URL Generation and ConfigurationBuild Time Dimensions and PlaceholdersStatic Image Imports and Dimension InspectionBlur Placeholder Generation and SVG FormattingRequest Dispatch and Security ChecksParameter Validation and Configuration HandlingSSRF Prevention and Remote Pattern MatchingImage Transformation and Format NegotiationContent-Type Detection and Bypass HandlingSharp Initialization and Resizing PipelineFormat Negotiation and API SurfaceCache Storage and ETag RevalidationDisk LRU and Incremental Cache IntegrationETag Generation and Conditional Request HandlingRelated