PhoenixImage

On-the-fly image resizing for Phoenix with disk caching and a responsive <.image> component.

Images are resized on first request using libvips (via the image library), cached to disk, and served with immutable cache headers. The <.image> component renders <img> tags with correct width, height, and srcset attributes to prevent layout shift and support retina displays.

Installation

Add phoenix_image to your list of dependencies in mix.exs:

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

Setup

1. Configure

# config/config.exs
config :phoenix_image, otp_app: :my_app

2. Add to supervision tree

# lib/my_app/application.ex
def start(_type, _args) do
  children = [
    # ...
    PhoenixImage,
    MyAppWeb.Endpoint
  ]

  Supervisor.start_link(children, strategy: :one_for_one)
end

3. Add route

# lib/my_app_web/router.ex
scope "/" do
  forward "/images", PhoenixImage.Plug
end

4. Import the component

# lib/my_app_web.ex
defp html_helpers do
  quote do
    # ...
    import PhoenixImage.Component
  end
end

Usage

The <.image> component

Place your source images in priv/static/ as usual. Then use the component in your templates:

<%# Crop to exact dimensions (attention-based crop) %>
<.image src="/images/photo.jpg" fill={{400, 300}} alt="A photo" class="rounded" />

<%# Resize by width, height calculated proportionally %>
<.image src="/images/photo.jpg" width={800} alt="A photo" />

<%# Resize by height, width calculated proportionally %>
<.image src="/images/photo.jpg" height={600} alt="A photo" />

<%# Fit within max dimensions, no crop %>
<.image src="/images/photo.jpg" max={{800, 600}} alt="A photo" />

<%# Serve original size (still sets width/height to prevent layout shift) %>
<.image src="/images/photo.jpg" original alt="A photo" />

The component automatically:

Resize operations

Operation URL pattern Description
fill/images/fill-400x300/path Crop to exact dimensions using attention detection
width/images/width-800/path Resize by width, proportional height
height/images/height-600/path Resize by height, proportional width
max/images/max-800x600/path Fit within bounds, no crop
original/images/original/path Serve original file

Allowed dimensions

For security, only whitelisted dimensions are accepted. The defaults are:

64, 96, 100, 150, 192, 200, 288, 300, 400, 600, 800, 900, 1200, 1536

Requests with other dimensions return a 400 error.

Configuration

All options are set via application config under :phoenix_image:

config :phoenix_image,
  # Required: which OTP app&#39;s priv/static to read source images from
  otp_app: :my_app,

  # URL prefix for image routes (must match your `forward` path)
  # Default: "/images"
  prefix: "/images",

  # Whitelisted dimensions (width/height values)
  # Default: [64, 96, 100, 150, 192, 200, 288, 300, 400, 600, 800, 900, 1200, 1536]
  allowed_dims: [64, 96, 100, 150, 192, 200, 288, 300, 400, 600, 800, 900, 1200, 1536],

  # JPEG output quality (1-100)
  # Default: 90
  quality: 90,

  # Directory within priv/ where source images live
  # Default: "priv/static"
  source_dir: "priv/static",

  # Directory within priv/ for cached resized images
  # Default: "priv/static/cache/images"
  cache_dir: "priv/static/cache/images"

Cache management

Resized images are cached to disk on first request. Subsequent requests serve the cached file directly with Cache-Control: public, max-age=31536000, immutable.

To clear the cache and force regeneration:

mix phoenix_image.reset_cache

How it works

  1. A request hits /images/fill-400x300/images/photo.jpg
  2. PhoenixImage.Plug parses the operation and validates dimensions
  3. PhoenixImage.Processor checks for a cached version on disk
  4. If not cached, it resizes the source image using Image.thumbnail/3 (libvips)
  5. The result is written to the cache directory and served with immutable headers
  6. Meanwhile, PhoenixImage.Component reads the original image dimensions (cached in ETS via PhoenixImage.Dimensions) to set correct width/height attributes at render time

Requirements

On macOS: brew install libvips On Ubuntu/Debian: apt-get install libvips-dev

License

MIT