---
title: "Introduction"
manual: "Image Optimization for TYPO3"
version: "2.6"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-image-optimize:introduction@2.6"
source: "Introduction/Index.rst"
rendered: "2026-09-25T11:13:49+00:00"
---

# Introduction {#introduction}

## What does it do? {#introduction-what-it-does}

The *Image Optimization for TYPO3* extension (`nr_image_optimize`)
compresses images in TYPO3 on three independent layers:

-   ****On upload****

    A PSR-14 event listener runs lossless optimization whenever a
    file is added to or replaced in a FAL storage
    (`AfterFileAddedEvent` / `AfterFileReplacedEvent`). The
    listener delegates to the installed `optipng` / `gifsicle`
    / `jpegoptim` binaries.

-   ****On demand in the frontend****

    A PSR-15 middleware intercepts every request that starts with
    `/processed/` and delegates to the
    [\\Netresearch\\NrImageOptimize\\Processor](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:netresearch-nrimageoptimize-processor@2.6). The
    processor parses the URL, loads the original via
    [Intervention Image](https://image.intervention.io/),
    produces a resized/recropped variant, optionally writes a
    matching WebP and AVIF sidecar, and streams the best result
    back to the client. Variants are cached on disk and served
    with long-lived HTTP caching headers.

-   ****In bulk from the CLI****

    Two Symfony Console commands iterate the FAL index:
    [nr:image:optimize](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:usage-cli-optimize@2.6) compresses every
    eligible file, [nr:image:analyze](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:usage-cli-analyze@2.6)
    reports optimization potential as a fast heuristic without
    touching any file.

All three layers share a common
[\\Netresearch\\NrImageOptimize\\Service\\ImageOptimizer](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:netresearch-nrimageoptimize-service-imageoptimizer@2.6)
service for tool resolution and process orchestration.

## Performance model {#introduction-performance}

Every image on a page costs the server twice: once when the page is
rendered, and once when a browser fetches the image. What sets this
extension apart from TYPO3 core's `f:image` / `ImageService`
pipeline is *when* the expensive part -- decoding, resizing,
encoding -- happens.

**TYPO3 core** processes every referenced image *while the page
renders*, inside the request that produces the HTML. On a cold page
cache the visitor waits for all of it before the first byte of HTML
arrives, images below the fold and in hidden sliders included, and
one PHP process does the work for all of them, one after another.

**This extension** only builds a `/processed/...` URL string while
the page renders. Nothing is decoded or written at that point. The
work happens later, in a *separate request per image*, when -- and
only if -- the browser asks for that image. Those requests are
spread across all PHP-FPM workers, and once a variant exists the web
server serves it as a static file without touching TYPO3 at all.

### What that is worth {#introduction-performance-measured}

The numbers below come from the benchmark that ships with this
extension (`make benchmark`, see [Reproduce it](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:introduction-performance-reproduce@2.6)).
Both pipelines render the same 24 photos (3000×2000 JPEG) as 800×600
crops on otherwise identical pages; every scenario is a state a real
site is in at some point. Medians of three visits, TYPO3 14.3.6,
PHP 8.4, ImageMagick 7, Apache + PHP-FPM in Docker on a developer
laptop -- absolute values will differ on your hardware, the ratios
will not.

![Bar chart: time to first byte of the HTML document per scenario, core versus extension](../Images/Benchmark/ttfb.svg)

![Bar chart: time until the page is fully loaded per scenario, core versus extension](../Images/Benchmark/load.svg)

![Bar chart: server CPU time per visit per scenario, core versus extension](../Images/Benchmark/server-cpu.svg)

![Bar chart: variant files written per visit per scenario, core versus extension](../Images/Benchmark/variants-written.svg)

![Bar chart: requests handled by PHP per visit per scenario, core versus extension](../Images/Benchmark/php-requests.svg)

In short:

-   **First visitor after a deployment or cache flush:** HTML in
    0.12 s instead of 8.4 s; complete page in 3.0 s instead of 8.4 s.
-   **Lazy loading and hidden content** actually save work: only
    images the browser fetches are ever processed.
-   **Render time is independent of image count and size.** A page
    with 200 images renders as fast as one with two; image-heavy
    pages no longer risk `max_execution_time` during render.
-   **Purged or lost variants are not an outage.** They are
    regenerated on the next request instead of serving 404s until
    someone flushes the page cache.
-   **Steady state is identical:** a page-cache hit plus static files
    in both cases.
-   **Per image, the extension does more work by default** -- three
    formats, 13.5 s against 8.3 s of CPU for 24 images -- and less for
    the same single format (6.9 s against 9.2 s); it delivers fewer
    bytes where the browser accepts AVIF or WebP.

### Reproduce it {#introduction-performance-reproduce}

The benchmark is part of the test suite, so the claims above are
re-measured rather than remembered:

-   `make benchmark` provisions TYPO3, the extension, Apache and
    PHP-FPM in Docker, drives Chromium through the seven scenarios
    with both pipelines and rewrites the charts in
    `Documentation/Images/Benchmark/` together with the raw
    `results.json` (every iteration, not just the medians). Only
    Docker is required; see `Tests/E2E/README.md`.
-   The suite *asserts* the architectural claims -- zero variants
    written during render, lower cold-cache TTFB, no broken images
    after a purge, fewer images processed under lazy loading -- and
    fails when they stop holding. Absolute timings are reported,
    never asserted.
-   `Tests/Functional/Benchmark/RenderCostTest.php` guards the
    core claim (render writes nothing; core's `ImageService`
    processes everything) in every CI run, without a browser.

## Features {#introduction-features}

-   **Automatic optimization on upload.** In-place lossless
    compression without re-encoding. Unsupported extensions,
    offline storages, and missing binaries are handled
    transparently -- the listener never raises.
-   **Bulk CLI commands.** Streaming iteration over `sys_file`
    keeps memory usage flat on large installations. Progress bar
    with cumulative savings.
-   **No image processing during page render.** Rendering only
    emits `/processed/` URLs; each variant is produced in its own
    request, when a browser first asks for it, and served as a static
    file from then on (see [Performance model](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:introduction-performance@2.6)).
-   **Next-gen format support.** Automatic WebP and AVIF sidecar
    generation, served in the order AVIF, WebP, original format,
    with per-URL `skipWebP` / `skipAvif` opt-outs.
-   **Responsive images.**
    [\\Netresearch\\NrImageOptimize\\ViewHelpers\\SourceSetViewHelper](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:netresearch-nrimageoptimize-viewhelpers-sourcesetviewhelper@2.6)
    emits `<img>` tags with density-based or width-based
    `srcset` \+ `sizes`.
-   **Render modes.** Choose between `cover` and `fit` resize
    strategies per URL.
-   **Fetch priority.** Native `fetchpriority` attribute
    support for Core Web Vitals (LCP) tuning.
-   **PSR-14 extension points.**
    [\\Netresearch\\NrImageOptimize\\Event\\ImageProcessedEvent](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:netresearch-nrimageoptimize-event-imageprocessedevent@2.6)
    and
    [\\Netresearch\\NrImageOptimize\\Event\\VariantServedEvent](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:netresearch-nrimageoptimize-event-variantservedevent@2.6)
    let integrators observe the pipeline.
-   **Driver abstraction.** Imagick is preferred when the
    extension is loaded; GD is used as a fallback. The
    `ImageReaderInterface` adapter (see
    [ImageManager abstraction](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:developer-image-manager@2.6)) hides the Intervention
    Image v3/v4 API difference.
-   **Backend maintenance module.** View statistics about
    processed images, check prerequisites, and clear the cache
    from the TYPO3 backend.
-   **Security.** Path traversal is blocked at URL parsing time,
    quality and dimension values are clamped to safe ranges,
    PSR-7 responses replace direct `header()` / `exit` calls.

## Requirements {#introduction-requirements}

-   PHP 8.2, 8.3, 8.4, or 8.5.
-   TYPO3 13.4 or 14.
-   Imagick **or** GD PHP extension.
-   Intervention Image 3.11+ (installed automatically via
    Composer).

## Optional optimizer binaries {#introduction-optional-binaries}

The on-upload listener and the CLI commands only compress files
when a matching binary is available in `$PATH`:

-   **`optipng`**

    Lossless PNG compression.

-   **`gifsicle`**

    Lossless GIF compression.

-   **`jpegoptim`**

    Lossless (default) or lossy JPEG compression.

Paths can be pinned per binary via the `OPTIPNG_BIN`,
`GIFSICLE_BIN`, and `JPEGOPTIM_BIN` environment variables. A
set-but-invalid override is treated as authoritative: the tool
is reported unavailable rather than silently falling back to
`$PATH`. `$PATH` lookups also verify `is_executable()`.

See [Optional: install optimizer binaries](https://docs.typo3.org/permalink/netresearch/nr-image-optimize:installation-optional-binaries@2.6) for package-manager
snippets.

## Recommended extensions {#introduction-recommended-extensions}

-   **[imageoptimizer](https://github.com/christophlehmann/imageoptimizer)**

    Alternative TYPO3 image optimization extension that
    integrates a broader set of external binaries with the core
    image processing pipeline.
