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, andpng. - High Performance: Powered by
libvipsvia theimagelibrary for extremely fast processing. - Built-in Caching Headers: Automatically sets
Cache-Controlheaders 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"}
]
endPhoenixImage.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"}
]
endUsage
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, []
endAs a Component (<.image />)
Add to your Phoenix components module:
defmodule MyAppWeb.CoreComponents do
use Phoenix.Component
import PhoenixImage.Component, only: [image: 1]
endUse in HEEx:
<.image
src="https://example.com/photo.jpg"
alt="Scenic view"
width={1200}
height={800}
sizes="100vw"
/>Required sizing rule:
fill={true}OR bothwidthandheight.
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 absolutesrcURLs in addition to the request host.
Query Parameters
src(required): source image location.- Absolute
http://orhttps://URL, or - Root-relative path like
/images/logo.png(resolved against request host).
- Absolute
w(optional): positive integer width, max8192.h(optional): positive integer height, max8192.q(optional): quality from1to100.f(optional): output formatwebp|avif|jpg|png. Default:webp.upscale(optional):true|false. Default:false.
Resize Behavior
wonly: scales to width, preserves aspect ratio.honly: 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 to2xsource 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=webpThe response will include:
- The processed image binary.
Content-Type: image/webp.Cache-Control: public, max-age=31536000, immutable.x-phoenix-image-upscale: skippedwhen a requested enlargement was clamped.
Relative src Example
GET /images/optimize?src=/images/logo.png&w=320Error Semantics
400 Bad Request: missing/invalid parameters.403 Forbidden:srchost 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
libvipsto be installed on your system.- macOS:
brew install vips - Linux:
apt install libvips-dev(or equivalent for your distribution)
- macOS:
Compatibility
- Elixir
~> 1.15 - Plug
~> 1.19 - Optional component dependency:
phoenix_live_view ~> 1.0 - System dependency:
libvipswith desired output codecs (webp,avif, etc.)
Security Notes
- Validate
srcinputs and restrict with:allowed_hostsfor 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 pushingHEADand tag.--skip-precommit: skipmix precommit.--remote origin: choose push remote.