Skip to main content
Docs menu

Image filters

Last updated Edit this page View as Markdown

Image filters in mod_pagespeed 2.1 for Apache, nginx and IIS: recompression, WebP and opt-in AVIF conversion, resizing, lazy loading and responsive images.

On this page

Overview

mod_pagespeed 2.1 includes image filters for recompression, format conversion, resizing, and inlining that cut page weight without touching the original files on disk. The master filter rewrite_images is a CoreFilter and enables several sub-filters by default. Enable additional image filters individually for finer control. The same filter names and directives apply across the in-process module on Apache, nginx, and IIS.

IIS syntax

On IIS, use the same filter names with the pagespeed prefix in pagespeed.config (no semicolons):

pagespeed EnableFilters rewrite_images
pagespeed ImageRecompressionQuality 75

See IIS configuration for the full file format reference.

Quick reference

FilterCoreOFBDescriptionSafe
rewrite_imagesYes-Master filter; enables recompress, resize, inline sub-filtersYes
recompress_imagesvia rewrite_imagesYesRecompress and convert images (lossy re-encode)Yes
recompress_jpegvia rewrite_imagesYesRecompress JPEG imagesYes
recompress_pngvia rewrite_imagesYesRecompress PNG imagesYes
recompress_webpvia rewrite_imagesYesRecompress WebP imagesYes
convert_jpeg_to_progressivevia rewrite_imagesYesConvert large JPEGs to progressive encodingYes
convert_jpeg_to_webpvia rewrite_imagesYesServe WebP to capable browsersYes
convert_png_to_jpegvia rewrite_imagesYesConvert opaque PNGs to JPEGYes
convert_gif_to_pngvia rewrite_imagesYesConvert GIF to PNGYes
convert_to_webp_animatedNoNoConvert animated GIF to animated WebPTest first
convert_to_webp_losslessvia rewrite_imagesNoUse lossless WebP instead of lossyTest first
convert_jpeg_to_avifNoNoServe AVIF for photographic JPEG sourcesTest first
convert_to_avif_losslessNoNoServe lossless AVIF for PNG/GIF sourcesTest first
convert_to_avif_animatedNoNoServe AVIF for animated sourcesTest first
recompress_avifNoNoRe-encode AVIF images you already serveTest first
strip_image_color_profilevia rewrite_imagesYesRemove ICC color profilesYes
strip_image_meta_datavia rewrite_imagesYesRemove EXIF and other metadataYes
jpeg_subsamplingvia rewrite_imagesYesDownsample JPEG color channelsYes
resize_imagesvia rewrite_images-Resize images to declared width/heightYes
resize_rendered_image_dimensionsNo-Resize images to rendered dimensions via JSTest first
inline_imagesvia rewrite_images-Inline small images as data: URIsYes
responsive_imagesNoNoGenerate srcset attributesTest first
lazyload_imagesNoNoDefer offscreen image loadingTest first
inline_preview_imagesNoNoShow low-quality placeholder before full loadTest first
resize_mobile_imagesNoNoServe smaller images to mobile devicesTest first
dedup_inlined_imagesNoNoDeduplicate repeated inlined imagesYes
sprite_imagesNoNoCombine CSS background images into spritesTest first
insert_image_dimensionsNoNoAdd width/height to <img> tagsTest first
in_place_optimize_for_browserNoNoRetired: accepted with a warning, no effectRetired
extend_cache_imagesvia extend_cacheNoContent-hashed image URLs with a one-year cacheYes
responsive_images_zoomNoNoZoom-aware srcset selection for responsive_imagesTest first
insert_img_dimensionsNoNoAlternate spelling of insert_image_dimensionsTest first
experiment_collect_mob_image_infoNoNoMobilization experiment data collectionDangerous set

Core = enabled by default in the CoreFilters set. OFB = enabled by OptimizeForBandwidth mode.

Master filter: rewrite_images

Full guide →

What it does

rewrite_images is the compound image filter: one name switches on the whole optimization pipeline, covering recompression, format conversion, resizing to declared dimensions, metadata stripping, and inlining of small images. Every <img> on the page, and every image referenced from CSS, is a candidate. If optimization finishes within the rewrite deadline, the first view already gets the .pagespeed.ic. URL; otherwise that view keeps the original and later views get the optimized one. Live demo: rewrite_images.

<!-- before -->
<img src="/photos/team.jpg" width="400" height="300" />

<!-- after, to a WebP-capable browser: recompressed, resized, cache-extended -->
<img src="/photos/400x300xteam.jpg.pagespeed.ic.HASH.webp" width="400" height="300" />

When it helps and when it does not

The compound pays on sites whose images are uploaded as-is: photos exported at full resolution, opaque PNGs that would be far smaller as JPEG or WebP, files carrying camera metadata nobody reads. A site that already runs a disciplined image pipeline, with build-time resizing, modern formats, and stripped metadata, leaves it little to do, while the CPU cost of the first rewrite of every image still applies. It also does nothing for images on domains the configuration does not authorize, or for sources over ImageResolutionLimitBytes (default 33554432 bytes).

How it decides

Each member filter applies its own test: resizing only happens when width and height are declared, WebP only when the browser advertises support, inlining only below ImageInlineMaxBytes. Every recompression or conversion is kept only when the result is smaller than the original, by the margin ImageLimitOptimizedPercent sets (default 100: any reduction). A losing rewrite is remembered, so a losing image is not re-encoded on every request. Some members are also part of OptimizeForBandwidth; the compound itself is a CoreFilter.

Sub-filters enabled by default

When rewrite_images is active, the following sub-filters are enabled automatically:

  • recompress_images (and its sub-filters: recompress_jpeg, recompress_png, recompress_webp)
  • convert_jpeg_to_progressive
  • convert_jpeg_to_webp
  • convert_png_to_jpeg
  • convert_gif_to_png
  • convert_to_webp_lossless
  • strip_image_color_profile
  • strip_image_meta_data
  • jpeg_subsampling
  • resize_images
  • inline_images

Risks

  • Recompression is lossy. The default quality levels are conservative, but check quality-critical imagery such as logos and screenshots with text after enabling.
  • The first optimization of each image costs server CPU; ImageMaxRewritesAtOnce (default 8) bounds how many run in parallel on a cold cache.
  • Verify with the X-Mod-Pagespeed response header and a ?PageSpeedFilters=-rewrite_images comparison; Is it working? has the steps.

Directives

Apache:

ModPagespeedEnableFilters rewrite_images

nginx:

pagespeed EnableFilters rewrite_images;

To disable a specific sub-filter while keeping rewrite_images active:

Apache:

ModPagespeedEnableFilters rewrite_images
ModPagespeedDisableFilters convert_jpeg_to_webp

nginx:

pagespeed EnableFilters rewrite_images;
pagespeed DisableFilters convert_jpeg_to_webp;

Recompression filters

Full guide → · Also: recompress_jpeg, recompress_png, recompress_webp

What they do

The recompression filters reduce image file size by re-encoding images at an optimal quality level. They preserve visual quality while removing encoding inefficiencies, and each one handles exactly the format its name says, so a format you do not want re-encoded can be turned off by name without touching the others.

How each format is recompressed

recompress_jpeg re-encodes JPEG images that reach it. It runs after the conversion candidates: a JPEG that was not converted to WebP or AVIF for this request is re-encoded at JpegRecompressionQuality, which defaults to -1, meaning it follows ImageRecompressionQuality (default 85). Only when ImageRecompressionQuality is itself -1 does the module keep the source file’s own quality.

recompress_jpeg keeps a re-encode only when it is smaller than the original by the margin ImageLimitOptimizedPercent sets (default 100: any reduction at all), and a losing re-encode is remembered so the same JPEG is not re-encoded on every request. The filter is a CoreFilter through rewrite_images and also part of OptimizeForBandwidth; disabling it by name while keeping the compound on leaves the other formats recompressing.

recompress_png re-encodes PNG images losslessly. The PNG optimizer re-encodes the image and keeps the result only when it is smaller, under the same ImageLimitOptimizedPercent margin as the other formats; a losing result is remembered. This filter never makes a PNG lossy: turning a photographic PNG into a JPEG or a WebP is the conversion filters’ decision, and when that decision does not apply or loses, the lossless re-encode this filter controls is what runs.

recompress_png is also the path a resized PNG takes, because resizing re-encodes the file. In OptimizeForBandwidth mode the smaller bytes replace the original response in place, with the URL unchanged. The filter is a CoreFilter through rewrite_images.

recompress_webp re-encodes WebP images the page already serves, at WebpRecompressionQuality (default 80). It does not create WebP; converting JPEG or PNG sources to WebP is the conversion filters’ job. Animated WebP is never recompressed, and there is no fallback conversion of WebP to JPEG for browsers without WebP support: such sources are left alone rather than transcoded.

recompress_webp keeps a re-encode only when it is smaller under ImageLimitOptimizedPercent, and a losing result is remembered. A small-screen client gets a lower quality still: WebpRecompressionQualityForSmallScreens (default 70) applies to the requests the module classifies as small-screen. The filter is a CoreFilter through rewrite_images and part of OptimizeForBandwidth.

recompress_images is a convenience filter that enables recompress_jpeg, recompress_png, and recompress_webp together with convert_gif_to_png, convert_jpeg_to_progressive, convert_jpeg_to_webp, convert_png_to_jpeg, jpeg_subsampling, strip_image_color_profile, and strip_image_meta_data.

Directives

Apache:

ModPagespeedEnableFilters recompress_images
# Or individually:
ModPagespeedEnableFilters recompress_jpeg,recompress_png,recompress_webp

nginx:

pagespeed EnableFilters recompress_images;
# Or individually:
pagespeed EnableFilters recompress_jpeg,recompress_png,recompress_webp;

Configuration

ParameterDefaultDescription
ImageRecompressionQuality85General quality level for recompressed images (-1 to 100; -1 uses source quality)
JpegRecompressionQuality-1JPEG-specific quality; -1 uses ImageRecompressionQuality
WebpRecompressionQuality80WebP quality level

Apache:

ModPagespeedImageRecompressionQuality 85
ModPagespeedJpegRecompressionQuality 75

nginx:

pagespeed ImageRecompressionQuality 85;
pagespeed JpegRecompressionQuality 75;

Risks

Recompression is lossy. Setting quality too low produces visible artifacts. The default values are conservative and safe for most content. Photographic content can tolerate lower quality than screenshots or text-heavy images.

Format conversion filters

Full guides: convert_jpeg_to_progressive, convert_jpeg_to_webp, convert_png_to_jpeg, convert_gif_to_png, convert_to_webp_animated, convert_to_webp_lossless

What they do

Format conversion filters serve images in the most efficient format for each browser and image type:

  • convert_jpeg_to_progressive converts large baseline JPEGs to progressive encoding. Progressive JPEGs render incrementally and are often smaller for images above ~10 KB.
  • convert_jpeg_to_webp serves WebP versions of JPEG images to browsers that send Accept: image/webp. The original JPEG is preserved for other browsers.
  • convert_png_to_jpeg converts PNG images with no alpha channel (fully opaque) to JPEG, which is typically much smaller for photographic content.
  • convert_gif_to_png converts non-animated GIF images to PNG, which uses better compression.
  • convert_to_webp_animated converts animated GIF images to animated WebP. Not a CoreFilter.
  • convert_to_webp_lossless uses lossless WebP encoding instead of lossy. Produces larger files than lossy WebP but preserves every pixel. A CoreFilter, enabled by default through rewrite_images.

convert_jpeg_to_webp is the member of the family with the widest reach, because JPEG is where photographic weight usually sits. For a WebP-capable request, the filter encodes a WebP candidate at WebpRecompressionQuality (default 80) and keeps it when it is smaller than the original JPEG. WebP-capable requests (by Accept: image/webp or a known user agent) get HTML pointing at a .webp URL; others get the JPEG URL, so the HTML differs per browser; a CDN caching rewritten HTML must not share it across browsers. The filter runs in CoreFilters through rewrite_images and is listed in OptimizeForBandwidth, but in that level’s in-place mode no WebP is selected.

convert_png_to_jpeg turns a PNG into a JPEG only when the module’s image analysis says the file is photographic, meaning not sensitive to compression noise, and it has no transparent pixels. The analysis is what protects flat graphics: a logo or a diagram analyzes as non-photographic and keeps its PNG. On a photographic PNG without transparency the filter authorizes lossy encoding, and the format that ships depends on the browser: when the request prefers WebP and convert_jpeg_to_webp is on, the file becomes WebP rather than JPEG; otherwise it is re-encoded as JPEG at the JPEG quality setting. A PNG with transparency is never made a JPEG. The converted file is kept only when it is smaller (ImageLimitOptimizedPercent), and a PNG carrying a C2PA content-credentials manifest is left untouched so its provenance chain survives. The filter runs in CoreFilters through rewrite_images.

convert_gif_to_png re-encodes a non-animated GIF as PNG. Without this filter, and without one of the animated-format conversions or the low-resolution preview path that need the same decode, a GIF that is not resized passes through the module untouched: no other filter re-encodes it. A resized GIF is decoded and resized as a PNG, so with this filter on it ships as PNG (or, if photographic and the lossy conversions apply, as JPEG or WebP); it never goes back out as a GIF. The conversion is lossless in intent (a GIF’s at-most-256-color palette maps into PNG), and the result is kept only when it is smaller than the original GIF under ImageLimitOptimizedPercent. One non-obvious effect: the filter also unlocks lossy treatment of photographic GIFs, because the lossy decision path accepts a GIF only when it may first be treated as a PNG-style source. With the filter off, a photographic GIF is never turned into a JPEG or lossy WebP; it can still leave as lossless WebP or AVIF when those conversions are enabled, and otherwise stays a GIF. The filter runs in CoreFilters through rewrite_images.

Directives

Apache:

ModPagespeedEnableFilters convert_jpeg_to_progressive
ModPagespeedEnableFilters convert_jpeg_to_webp
ModPagespeedEnableFilters convert_png_to_jpeg
ModPagespeedEnableFilters convert_gif_to_png
ModPagespeedEnableFilters convert_to_webp_animated
ModPagespeedEnableFilters convert_to_webp_lossless

nginx:

pagespeed EnableFilters convert_jpeg_to_progressive;
pagespeed EnableFilters convert_jpeg_to_webp;
pagespeed EnableFilters convert_png_to_jpeg;
pagespeed EnableFilters convert_gif_to_png;
pagespeed EnableFilters convert_to_webp_animated;
pagespeed EnableFilters convert_to_webp_lossless;

Configuration

ParameterDefaultDescription
ProgressiveJpegMinBytes10240Minimum JPEG size before converting to progressive
WebpRecompressionQuality80Quality for lossy WebP conversion
WebpAnimatedRecompressionQuality70Quality for animated WebP conversion

Risks

  • convert_png_to_jpeg drops the alpha channel on opaque PNGs. mod_pagespeed checks for alpha before converting, but semi-transparent pixels at the boundary may cause subtle edge artifacts.
  • convert_jpeg_to_webp decides per request, so the rewritten HTML names a .webp image for some browsers and a JPEG for others. A CDN or proxy that caches the HTML must not serve one browser’s copy to another; test with your CDN configuration.
  • convert_to_webp_animated can produce large files for complex animations. Verify output sizes.

AVIF filters

Full guides: convert_jpeg_to_avif, convert_to_avif_lossless, convert_to_avif_animated, recompress_avif

What they do

Four filters transcode images to AVIF. Each one targets a different kind of source:

  • convert_jpeg_to_avif encodes photographic JPEG sources as AVIF.
  • convert_to_avif_lossless encodes flat-palette and screenshot-style sources (PNG, GIF) as lossless AVIF.
  • convert_to_avif_animated encodes animated sources as AVIF.
  • recompress_avif re-encodes AVIF images you already serve.

All four are opt-in. They sit outside rewrite_images and outside the CoreFilters set, so enabling rewrite_images alone produces no AVIF output — you have to name the AVIF filters you want.

AVIF is served only when the request advertises Accept: image/avif. There is no User-Agent sniffing. A client that does not advertise AVIF falls through the existing format order unchanged. The cache key folds in the client’s AVIF capability, so variants are stored and served per capability exactly as WebP variants are.

The AV1 encoder is statically linked into the module. There is no separate package or codec library to install.

Caveat: RewriteLevel AllFilters enables all four

“Opt-in” holds for filters you name explicitly. The four AVIF filters are not in the dangerous-filter set, so RewriteLevel AllFilters switches all of them on. If you run AllFilters, you are running the AVIF filters whether or not you listed them.

Directives

Apache:

ModPagespeedEnableFilters convert_jpeg_to_avif
ModPagespeedEnableFilters convert_to_avif_lossless
ModPagespeedEnableFilters convert_to_avif_animated
ModPagespeedEnableFilters recompress_avif

nginx:

pagespeed EnableFilters convert_jpeg_to_avif;
pagespeed EnableFilters convert_to_avif_lossless;
pagespeed EnableFilters convert_to_avif_animated;
pagespeed EnableFilters recompress_avif;

On IIS, use the same filter names in pagespeed.config without the trailing semicolon — see IIS configuration.

Configuration

ParameterDefaultDescription
AvifRecompressionQuality60AVIF quality level; -1 uses ImageRecompressionQuality
AvifRecompressionQualityForSmallScreens50AVIF quality for small-screen clients; -1 uses AvifRecompressionQuality
AvifAnimatedRecompressionQuality50Quality for animated AVIF output
AvifQualityForSaveData45AVIF quality for clients sending Save-Data: on
AvifTimeoutMs5000Wall-clock budget for one AVIF encode; the encode is abandoned when it is exceeded

These mirror the Webp* quality parameters: a value of -1 falls back to the more general setting, so you can set ImageRecompressionQuality alone and let AVIF follow it.

AvifTimeoutMs exists because AV1 still-image encoding is materially slower than WebP. The budget stops one expensive image from occupying a rewrite slot indefinitely; when it is hit the AVIF candidate is abandoned and the existing WebP or optimized-original output is served, so a timeout costs CPU but never a broken image. Raise it if large photographic sources are being skipped, lower it if AVIF encodes are crowding out other rewrites.

Apache:

ModPagespeedAvifRecompressionQuality 60
ModPagespeedAvifTimeoutMs 5000

nginx:

pagespeed AvifRecompressionQuality 60;
pagespeed AvifTimeoutMs 5000;

Risks

  • An AVIF candidate costs a full extra decode and encode per image. The module encodes an AVIF candidate and keeps it only if it beats the WebP or optimized-original candidate. Each candidate is produced from its own decode; the decode is not shared between formats. AV1 encoding is also slower than WebP encoding at comparable quality, so enabling AVIF measurably raises the CPU cost of the first rewrite of every image. Watch ImageMaxRewritesAtOnce and the rewrite deadline on busy origins.
  • Pick-smaller means some of that work is discarded. When the AVIF candidate is not smaller, it is dropped and the existing variant is served. The CPU is spent either way.
  • Images with C2PA content credentials are not converted. A source carrying a C2PA manifest is left alone by all four filters, so its provenance chain survives the pipeline.
  • Delivery depends on the Accept header reaching the module. As with convert_jpeg_to_webp, a CDN or proxy that rewrites Accept or ignores Vary: Accept can cross-serve formats. Test with your CDN configuration before rolling out.

Image stripping filters

Full guides: strip_image_color_profile, strip_image_meta_data, jpeg_subsampling

What they do

  • strip_image_color_profile removes embedded ICC color profiles from images. Most web browsers ignore ICC profiles, and they can add tens of kilobytes to a file.
  • strip_image_meta_data removes EXIF, XMP, and other metadata from images. This includes camera information, GPS coordinates, thumbnails, and editing history.
  • jpeg_subsampling downsamples JPEG chroma channels (4:2
    subsampling), reducing file size with minimal visual impact on photographic content.

Directives

Apache:

ModPagespeedEnableFilters strip_image_color_profile
ModPagespeedEnableFilters strip_image_meta_data
ModPagespeedEnableFilters jpeg_subsampling

nginx:

pagespeed EnableFilters strip_image_color_profile;
pagespeed EnableFilters strip_image_meta_data;
pagespeed EnableFilters jpeg_subsampling;

Risks

  • Stripping color profiles can cause subtle color shifts on wide-gamut displays or for images authored in non-sRGB color spaces. This affects a small fraction of web images.
  • Stripping metadata removes copyright and attribution information embedded in the file. The metadata is removed from the served variant only; original files on disk are not modified.

Resizing filters

Full guide → · Also: resize_rendered_image_dimensions, insert_image_dimensions

What they do

  • resize_images resizes images on the server to match the width and height attributes declared in the <img> tag. If an image is 2000x1500 but displayed at 400x300, mod_pagespeed serves a 400x300 variant.
  • resize_rendered_image_dimensions injects JavaScript that reports each image’s actual rendered dimensions on the client. On subsequent requests, mod_pagespeed resizes to the rendered size. Requires two page loads to take effect.
  • insert_image_dimensions adds explicit width and height attributes to <img> tags that lack them. This prevents layout shifts (CLS) but does not resize the image file itself. insert_img_dimensions is an accepted alternate spelling of the same filter.

insert_image_dimensions declares the intrinsic size of each image: it adds width and height attributes carrying the pixel dimensions of the image file itself, read from the copy the module decoded, to <img> and image <input> elements that declare no dimensions at all: no width attribute, no height attribute, and no dimension inside a style attribute. An element that already declares any of those is left alone. The browser uses the pair to reserve the image’s box before the file arrives, which is what removes the layout shift; the filter does not resize the image file and does not touch CSS sizing. It applies even to images the module decides not to optimize, as long as the file’s dimensions could be decoded. It does not apply when image URLs are preserved (ImagePreserveURLs, as under OptimizeForBandwidth). Images that inline_images turns into data: URIs get no dimensions from this filter; inline_images instead removes width and height attributes that match the image’s own size. Not a core filter; enable it by name.

Directives

Apache:

ModPagespeedEnableFilters resize_images
ModPagespeedEnableFilters resize_rendered_image_dimensions
ModPagespeedEnableFilters insert_image_dimensions

nginx:

pagespeed EnableFilters resize_images;
pagespeed EnableFilters resize_rendered_image_dimensions;
pagespeed EnableFilters insert_image_dimensions;

Configuration

ParameterDefaultDescription
ImageLimitResizeAreaPercent100Only resize if the result is this percentage of the original area or less

Risks

  • resize_images only works when width and height are present on the <img> tag. Images sized purely by CSS are not resized.
  • resize_rendered_image_dimensions injects JavaScript and requires two page loads. The first load serves the original image.
  • insert_image_dimensions can break responsive layouts that rely on the absence of explicit dimensions. Test with your CSS.

Inline and preview filters

Full guide → · Also: inline_preview_images, dedup_inlined_images, resize_mobile_images

What they do

  • inline_images replaces small image references with data: URIs, eliminating the HTTP request. Only images below ImageInlineMaxBytes are inlined.
  • inline_preview_images replaces full-size images with a low-quality inline placeholder that loads instantly, then swaps in the full image via JavaScript.
  • dedup_inlined_images replaces repeated inline data: URIs on the same page with JavaScript references to the first occurrence, reducing HTML size.
  • resize_mobile_images serves smaller images to mobile devices based on the User-Agent header. Enabling it also enables inline_preview_images, which it depends on.

inline_preview_images serves a low-quality preview first and the real image after. For each image the critical-images beacon has marked as above the fold, whose optimized size is between MinImageSizeLowResolutionBytes (default 3072 bytes) and MaxImageSizeLowResolutionBytes (default 1 MB), the optimizer encodes a tiny low-resolution version at quality 10, with profile, metadata and provenance stripped from the throwaway preview. The original src (and srcset, when present) is renamed to data-pagespeed-high-res-src (and -srcset). The preview is used only when it is small: it must fit MaxLowResImageSizeBytes (default: no cap) and be smaller than the full image under MaxLowResToFullResImageSizePercentage (default 100). On desktop the preview becomes the src and an onload handler on the image swaps in the full image; on mobile with aggressive rewriters on, the previews are injected as scripts after the last previewed image in the flush window, and a script at the end of the body swaps in the full images, on scroll when LazyloadHighresImages is enabled. The filter is beacon-driven: until the critical-images finder has data it does nothing, so a site’s first views are unchanged. It stands down when the page’s Content-Security-Policy forbids the inline scripts the swap relies on.

dedup_inlined_images shrinks pages that repeat the same inlined image. When two or more references have become identical data: URIs, the first occurrence keeps its bytes and gets an id; every later occurrence of the same data URI loses its src and instead carries a one-line inline script that copies the first image’s src back onto the element at run time. There is a floor below which dedup does not pay: a data URI must be longer than 185 bytes, roughly the size of the restoring snippet, or it is left as it is. The filter needs JavaScript, so it is disabled for user agents that cannot lazy-load images and for XMLHttpRequests, and it stands down inside <noscript> or when the page’s Content-Security-Policy forbids inline scripts. The num_dedup_inlined_images_candidates_found and num_dedup_inlined_images_candidates_replaced statistics in the admin console show how much it found and how much it replaced.

Directives

Apache:

ModPagespeedEnableFilters inline_images
ModPagespeedEnableFilters inline_preview_images
ModPagespeedEnableFilters dedup_inlined_images
ModPagespeedEnableFilters resize_mobile_images

nginx:

pagespeed EnableFilters inline_images;
pagespeed EnableFilters inline_preview_images;
pagespeed EnableFilters dedup_inlined_images;
pagespeed EnableFilters resize_mobile_images;

Configuration

ParameterDefaultDescription
ImageInlineMaxBytes3072Maximum image size in bytes to inline as a data: URI

Apache:

ModPagespeedImageInlineMaxBytes 4096

nginx:

pagespeed ImageInlineMaxBytes 4096;

Risks

  • Inlining increases HTML size. Inlined images are not cached separately by the browser. Set ImageInlineMaxBytes conservatively.
  • inline_preview_images adds JavaScript and a visible quality transition. Users see a blurry image before the full-resolution variant loads.
  • resize_mobile_images relies on User-Agent detection. Incorrect UA classification can serve wrong-sized images. It also pulls in inline_preview_images, so expect that filter’s placeholder-then-swap behavior when enabling it.

Lazy loading: lazyload_images

Full guide →

What it does

lazyload_images defers loading of images that are below the fold, reducing initial page weight and request count.

In v1.15.0+r18 and later, on browsers that support it (the large majority), the filter adds the native loading="lazy" and decoding="async" attributes and leaves image URLs untouched. Images known to be above the fold are not deferred and instead get fetchpriority="high". On older browsers — and in v1.15.0+r17 and earlier — the filter uses a JavaScript loader that loads images as the user scrolls them into view.

The LazyloadImagesMode directive controls this behavior: auto (the default) selects native or JavaScript per browser, native always uses the attributes, and js always uses the JavaScript loader (the behavior of v1.15.0+r17 and earlier).

When the filter has no data yet about which images are above the fold, it leaves the first images of the page alone so the largest visible image is not deferred. LazyloadImagesSkipFirst sets how many (default 1).

Directives

Apache:

ModPagespeedEnableFilters lazyload_images

nginx:

pagespeed EnableFilters lazyload_images;

Configuration

Select the mechanism and tune the above-the-fold protection:

Apache:

ModPagespeedLazyloadImagesMode auto
ModPagespeedLazyloadImagesSkipFirst 1

nginx:

pagespeed LazyloadImagesMode auto;
pagespeed LazyloadImagesSkipFirst 1;

To disable lazy loading for a specific image, add the data-pagespeed-no-defer attribute:

<img src="hero.jpg" data-pagespeed-no-defer />

Images that already carry a loading attribute are always left untouched, so hand-tuned markup keeps working.

Risks

  • In js mode (or auto on older browsers), JavaScript-based lazy loading can interfere with image-dependent layout calculations, print rendering, and automated testing tools. The JavaScript mechanism also stands down on pages whose Content-Security-Policy disallows inline scripts; native mode is unaffected by CSP.
  • Images that are immediately visible (above the fold) should not be lazy loaded. Above-fold detection is data-driven and imperfect; LazyloadImagesSkipFirst bounds the damage when no data is available, but pages with unusual layouts may still see a deferred visible image. Use data-pagespeed-no-defer on known-critical images.

Responsive images

Full guide →

What it does

responsive_images adds a srcset to <img> tags so a high-density screen fetches a sharper file and a 1x screen stops paying for pixels it cannot show. The module generates a resized variant for each configured pixel density, plus the full-sized original, and offers them as url N.x candidates; the browser picks by viewport and device pixel ratio. Not a core filter; enable it by name. Live demo: responsive_images.

<!-- before -->
<img src="/photos/team.jpg" width="400" height="300" />

<!-- after: the source file is 1600x1200, so the full-size candidate is 4x -->
<img
  src="/photos/400x300xteam.jpg.pagespeed.ic.HASH.jpg"
  width="400"
  height="300"
  srcset="/photos/600x450xteam.jpg.pagespeed.ic.HASH.jpg 1.5x,/photos/800x600xteam.jpg.pagespeed.ic.HASH.jpg 2x,/photos/1200x900xteam.jpg.pagespeed.ic.HASH.jpg 3x,/photos/xteam.jpg.pagespeed.ic.HASH.jpg 4x"
/>

When it helps and when it does not

It helps where the same page serves both ordinary screens and phones or high-density displays: the 1x visitor downloads the small file instead of the oversized original, and the 2x visitor gets a file that is actually sharp. It adds nothing for an image whose declared dimensions already equal the source file’s size: every candidate then resolves to the same file and no srcset is emitted. Every variant is a resized copy in the cache, so a page with many large images multiplies its storage; the responsive_images_zoom companion adds a script and refetches on zoom.

How it decides

The filter needs src, width and height on the <img>; an image without declared dimensions, an image that already carries a srcset, one marked data-pagespeed-no-transform, and 1x1 tracking pixels are all left alone. Densities come from ResponsiveImageDensities (default 1.5,2,3, each must be above zero). Each candidate is resized to the declared dimensions scaled by its density, and the full-sized original joins the list at the resolution its width actually represents. Candidates whose URL or final dimensions equal the previous candidate’s are dropped, so an image whose source file is only slightly larger than the 1x size gets a shorter list. If the highest-density variant turns out small enough to inline as a data: URI, it becomes the single src and no srcset is emitted. The filter is wired up only when resize_images is also enabled, which it is under CoreFilters through rewrite_images.

Directives

Apache:

ModPagespeedEnableFilters responsive_images

nginx:

pagespeed EnableFilters responsive_images;

Configuration

To control which pixel densities are generated (default 1.5,2,3), use ResponsiveImageDensities with a comma-separated list of numbers:

Apache:

ModPagespeedResponsiveImageDensities 1.5,2,3

nginx:

pagespeed ResponsiveImageDensities 1.5,2,3;

Risks

  • Generating multiple variants per image increases storage and cache requirements on the server.
  • If images have many density variants, the total bytes served across all variants can exceed the original single image. Monitor cache size and bandwidth.
  • Requires width and height attributes on <img> tags to calculate variant dimensions.
  • Verify with the X-Mod-Pagespeed response header and a ?PageSpeedFilters=-responsive_images comparison; Is it working? has the steps.

Sprite images

Full guide →

What it does

sprite_images merges small CSS background images into one sprite sheet and rewrites the stylesheet so each rule shows its own region: the background-image URL becomes the sheet at a .pagespeed.is. URL, and the background-position shifts to the element’s slice. A toolbar of icons that cost a request each then arrives in one fetch. Not a CoreFilter; enable it by name. Live demo: sprite_images.

/* before */
.icon-cart {
  background: url(/img/cart.png) no-repeat;
  width: 16px;
  height: 16px;
}
.icon-user {
  background: url(/img/user.png) no-repeat;
  width: 16px;
  height: 16px;
}

/* after: the images are stacked vertically in one sheet */
.icon-cart {
  background: url(/img/cart.png+user.png.pagespeed.is.HASH.png) no-repeat;
  width: 16px;
  height: 16px;
  background-position: 0 0;
}
.icon-user {
  background: url(/img/cart.png+user.png.pagespeed.is.HASH.png) no-repeat;
  width: 16px;
  height: 16px;
  background-position: 0 -16px;
}

When it helps and when it does not

Spriting is an HTTP/1.1 technique: it exists because browsers used to open few connections per host and every icon queued. With HTTP/2 multiplexing the latency benefit is minimal, and the costs remain. Changing one icon invalidates the whole sheet, and a page that shows two icons downloads every icon packed beside them. It can still pay on icon-heavy pages with a stable icon set and a large HTTP/1.1 audience; elsewhere, measure before keeping it.

How it decides

Only images referenced from a CSS background or background-image declaration are candidates; <img> tags never join a sprite. Only PNG or GIF backgrounds in rules that declare both width and height are sprited, and GIFs are converted to PNG in the sheet. The module must be able to fetch the image and learn its dimensions, and a declaration the CSS parser cannot understand is left alone rather than guessed at. Positions in the rewritten rules are computed from the packed layout: an existing background-position is shifted to the slice, and a rule without one gets a new background-position declaration.

Risks

  • Spriting increases cache invalidation scope: changing one image invalidates the entire sprite.
  • Only CSS background-image references are sprited. Inline <img> tags are not affected.
  • Verify with the X-Mod-Pagespeed response header and a ?PageSpeedFilters=-sprite_images comparison; Is it working? has the steps.

Directives

Apache:

ModPagespeedEnableFilters sprite_images

nginx:

pagespeed EnableFilters sprite_images;

In-place browser optimization

Full guide →

What it does

in_place_optimize_for_browser is retired. It used to vary in-place optimized images by the browser’s Accept header and add Vary: Accept; in-place optimization no longer produces browser-dependent bytes, so the filter has nothing left to do. The name is still accepted, with a warning, so an existing configuration keeps loading; it is no longer part of OptimizeForBandwidth. Remove it from your configuration.

Directives

Apache:

ModPagespeedEnableFilters in_place_optimize_for_browser

nginx:

pagespeed EnableFilters in_place_optimize_for_browser;

Risks

  • The Vary: Accept header can reduce CDN cache hit rates. Many CDNs handle Vary correctly, but verify with your provider.
  • Some proxy servers do not respect Vary headers and may serve the wrong format to clients.

Cache extension for images: extend_cache_images

Full guide →

What it does

Core filter, one of the three members of extend_cache. Rewrites image URLs to content-hashed .pagespeed.ce. URLs served with a one-year Cache-Control max-age, so browsers keep images for a year while a changed image gets a new URL. Use it when the origin cannot set long cache lifetimes itself; under CoreFilters it is already on through extend_cache, and rewrite_images produces content-hashed URLs of its own for every image it rewrites. Disabling it leaves extend_cache_css and extend_cache_scripts on. Live demo: extend_cache.

Directives

Apache:

ModPagespeedEnableFilters extend_cache_images

nginx:

pagespeed EnableFilters extend_cache_images;

Responsive images zoom

Full guide →

What it does

responsive_images_zoom adds a small script next to the srcset attributes that responsive_images generates, so that when the visitor zooms the page the browser picks a variant that stays sharp at the zoomed size instead of upscaling the one chosen for the original zoom level. Enable it together with responsive_images; on its own it does nothing. Not a core filter; test it with your image markup first.

Directives

Apache:

ModPagespeedEnableFilters responsive_images,responsive_images_zoom

nginx:

pagespeed EnableFilters responsive_images,responsive_images_zoom;

Risks

  • Adds a script to every page with responsive images.
  • Extra variants are fetched on zoom, which costs bandwidth on pages visitors zoom often.

Experimental: experiment_collect_mob_image_info

Full guide →

Collects image information for the retired page-mobilization experiment. In the dangerous set, which RewriteLevel AllFilters never enables; not for production.

pagespeed EnableFilters experiment_collect_mob_image_info;

Prioritize critical images

Full guide →

What it does

prioritize_critical_images sets fetchpriority="high" on the images the critical-images beacon has reported above the fold, so the browser front-loads the fetches that most influence Largest Contentful Paint. It is beacon-driven: the page is instrumented, real browsers report which images render above the fold, and the attribute is applied on subsequent responses. It rewrites attributes only and injects no scripts. Enabling the filter also turns on critical-images beaconing.

The filter is a strict no-op until beacon data is available — with no data it would have to guess, and a wrong guess would prioritize a below-the-fold image at the real LCP image’s expense. An author-supplied fetchpriority always wins, so hand-tuned markup is left untouched.

This filter is opt-in and is not part of any rewrite level. Enable it by name.

Directives

Apache:

ModPagespeedEnableFilters prioritize_critical_images

nginx:

pagespeed EnableFilters prioritize_critical_images;

Risks

  • Above-fold detection is data-driven and imperfect. The strict no-op-without-data behavior avoids the worst case, but on unusual layouts the beacon may still flag an image that is not truly the largest one.
  • The filter backs off on Save-Data requests, AMP documents, and disallowed URLs.

Tuning parameters

ParameterDefaultDescription
ImageRecompressionQuality85General quality for recompressed images (-1 to 100; -1 uses source quality)
JpegRecompressionQuality-1JPEG-specific quality; -1 uses ImageRecompressionQuality
WebpRecompressionQuality80WebP quality level
WebpAnimatedRecompressionQuality70Animated WebP quality level
ImageInlineMaxBytes3072Maximum image size in bytes to inline as a data: URI
ImageLimitOptimizedPercent100Only serve the optimized image if it is smaller by this percentage
ImageLimitResizeAreaPercent100Limit on resize area relative to original
ImageResolutionLimitBytes33554432Maximum image resolution in bytes to attempt to optimize
ImageMaxRewritesAtOnce8Server-wide limit on parallel image optimizations

Apache:

ModPagespeedImageRecompressionQuality 85
ModPagespeedJpegRecompressionQuality 75
ModPagespeedWebpRecompressionQuality 80
ModPagespeedWebpAnimatedRecompressionQuality 70
ModPagespeedImageInlineMaxBytes 3072
ModPagespeedImageLimitOptimizedPercent 100
ModPagespeedImageLimitResizeAreaPercent 100
ModPagespeedImageResolutionLimitBytes 33554432
ModPagespeedImageMaxRewritesAtOnce 8

nginx:

pagespeed ImageRecompressionQuality 85;
pagespeed JpegRecompressionQuality 75;
pagespeed WebpRecompressionQuality 80;
pagespeed WebpAnimatedRecompressionQuality 70;
pagespeed ImageInlineMaxBytes 3072;
pagespeed ImageLimitOptimizedPercent 100;
pagespeed ImageLimitResizeAreaPercent 100;
pagespeed ImageResolutionLimitBytes 33554432;
pagespeed ImageMaxRewritesAtOnce 8;

Design background: how server-side image rewriting works

The image filters above descend from a 2010 design for server-side image rewriting. Four invariants from that design still shape how rewrite_images works:

  1. Fetch and cache the original bytes asynchronously. A rewrite never blocks the response. On the first request the original image is served and the optimization runs in the background (bounded by RewriteDeadlinePerFlushMs); the optimized variant is served from cache on subsequent requests.
  2. Compare displayed dimensions to natural dimensions. When the page uses an image smaller than the source, resize_images rescales it to the size actually rendered instead of shipping full-resolution pixels the browser will only shrink.
  3. Recompress per format, and only keep a smaller result. Each format has its own codec strategy (recompress_jpeg, recompress_png, recompress_webp). A conversion (convert_jpeg_to_webp, convert_png_to_jpeg, convert_gif_to_png) or recompression is kept only when the output is smaller, which is what ImageLimitOptimizedPercent enforces.
  4. Cache the negative result too. If no variant is meaningfully smaller, the original URL is kept, and mod_pagespeed remembers that so it does not re-attempt a losing rewrite on every request.

The output formats have changed since 2010: the native module transcodes to WebP through rewrite_images, and to AVIF through opt-in filters enabled separately from it. The mod_pagespeed 2.1 optimizer worker adds SVG auto-vectorization and Jpegli through its own independent optimization engine. The four invariants above describe how the native module’s rewrite_images pipeline works. For how this works in production, see Automatic WebP/AVIF on nginx: One Decode, 37 Variants and Image optimization cost: self-hosted vs CDN.

Adapted from the original mod_pagespeed image-rewriting design (Google, 2010), an open-source project now maintained by We-Amp B.V. Original material © Google Inc., released under the Apache License 2.0. mod_pagespeed and PageSpeed are trademarks of Google LLC. We-Amp B.V. is not affiliated with, endorsed by, or sponsored by Google, and maintains the open-source mod_pagespeed project independently.

See also

Search