An on-demand image optimization library for Phoenix applications. phx_image provides a Plug that fetches source images, resizes/transcodes them, and serves them with caching headers. It also provides a Next.js-like <.image /> function component for Phoenix templates.

Features

  • On-Demand Optimization: Fetch, resize, and convert images on the fly via HTTP.
  • Aspect-Preserving Resizing: Keep original aspect ratio while fitting requested dimensions.
  • Multiple Formats: Supports outputting as webp, avif, jpg, and png.
  • High Performance: Powered by libvips via the image library for extremely fast processing.
  • Built-in Caching Headers: Automatically sets Cache-Control headers for long-term browser caching.

Installation

The package can be installed by adding phx_image to your list of dependencies in mix.exs:

def deps do
  [
    {:phx_image, "~> 0.1.0"}
  ]
end

PhoenixImage.Component depends on :phoenix_live_view. Add it explicitly if you use the component:

def deps do
  [
    {:phx_image, "~> 0.1.0"},
    {:phoenix_live_view, "~> 1.0"}
  ]
end

Usage

Demo App

This repo includes a Phoenix demo app in demo/ for manual testing.

cd demo
mix deps.get
mix phx.server

Open http://localhost:4000 and use the form to test /images/optimize.

As a Plug

You can mount the PhoenixImage.Plug in your Phoenix router or as part of a standalone Plug pipeline:

# In your router.ex
scope "/images" do
  pipe_through :browser
  get "/optimize", PhoenixImage.Plug, []
end

As a Component (<.image />)

Add to your Phoenix components module:

defmodule MyAppWeb.CoreComponents do
  use Phoenix.Component
  import PhoenixImage.Component, only: [image: 1]
end

Use in HEEx:

<.image
  src="https://example.com/photo.jpg"
  alt="Scenic view"
  width={1200}
  height={800}
  sizes="100vw"
/>

Required sizing rule:

  • fill={true} OR both width and height.

Configure component defaults:

config :phx_image, :image_component,
  optimize_path: "/images/optimize",
  device_sizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
  image_sizes: [16, 32, 48, 64, 96, 128, 256, 384],
  allowed_hosts: ["example.com", "cdn.example.com"]

Plug Options

PhoenixImage.Plug supports:

  • :cache_control (default: public, max-age=31536000, immutable)
  • :allowed_hosts (default: []): extra hosts allowed for absolute src URLs in addition to the request host.

Query Parameters

  • src (required): source image location.
    • Absolute http:// or https:// URL, or
    • Root-relative path like /images/logo.png (resolved against request host).
  • w (optional): positive integer width, max 8192.
  • h (optional): positive integer height, max 8192.
  • q (optional): quality from 1 to 100.
  • f (optional): output format webp|avif|jpg|png. Default: webp.
  • upscale (optional): true|false. Default: false.

Resize Behavior

  • w only: scales to width, preserves aspect ratio.
  • h only: scales to height, preserves aspect ratio.
  • w + h: fits inside the box while preserving aspect ratio (does not crop to exact dimensions).
  • By default, requests that would enlarge the image are clamped to source size.
  • With upscale=true, enlarge is allowed up to 2x source size.

Example

Requesting a 400px wide WebP version of an external image:

GET /images/optimize?src=https://example.com/large-photo.jpg&w=400&f=webp

The response will include:

  • The processed image binary.
  • Content-Type: image/webp.
  • Cache-Control: public, max-age=31536000, immutable.
  • x-phoenix-image-upscale: skipped when a requested enlargement was clamped.

Relative src Example

GET /images/optimize?src=/images/logo.png&w=320

Error Semantics

  • 400 Bad Request: missing/invalid parameters.
  • 403 Forbidden: src host is not same-host and not in :allowed_hosts.
  • 404 Not Found: upstream source returned 404.
  • 500 Internal Server Error: upstream/image-processing failures.

Requirements

  • libvips: This library requires libvips to be installed on your system.
    • macOS: brew install vips
    • Linux: apt install libvips-dev (or equivalent for your distribution)

Compatibility

  • Elixir ~> 1.15
  • Plug ~> 1.19
  • Optional component dependency: phoenix_live_view ~> 1.0
  • System dependency: libvips with desired output codecs (webp, avif, etc.)

Security Notes

  • Validate src inputs and restrict with :allowed_hosts for remote fetches.
  • Avoid exposing unrestricted optimizer endpoints on untrusted networks.

License

Apache-2.0. See LICENSE.

Documentation can be generated locally with:

mix docs

Docs are published automatically to GitLab Pages from the default branch.

Releasing

Use the custom task to run preflight checks, tag, push, and publish:

mix release.publish --yes

Useful flags:

  • --no-publish: run checks and create/push tag without publishing to Hex.
  • --no-tag: publish without creating a tag.
  • --no-push: avoid pushing HEAD and tag.
  • --skip-precommit: skip mix precommit.
  • --remote origin: choose push remote.