While Astro is awesome when it comes to build responsive images for static sites at built time, it lacks the flexibility needed for dynamic image processing in a SSR (server-side rendering) context. This is where ipx comes in handy, providing a powerful image proxy that can be integrated as a middleware into your Astro project.

The Problem: Image Optimization at Runtime
At my day job, we are currently working on a digital cafeteria menu system powered by Astro that requires real-time image processing capabilities. The challenge lies in dynamically resizing, converting formats, and optimizing images without pre-processing them at build time.
Since our service won’t receive much traffic, and our goal is to keep things simple yet performant, we decided to look for a solution that would allow us to handle image transformation within Astro instead of relying on separate services.
ipx: On-Demand Image Processing
ipx from the UnJS ecosystem is a powerful, high-performance image proxy service based on Sharp and libvips. It can be used as a standalone server or - like in this case - as middleware in your Astro project. ipx supports various image transformations such as resizing, cropping, format conversion, and more, all on-the-fly.
Integrating ipx with Astro Middleware
Here’s how you can quickly set up ipx as middleware within your Astro project:
Middleware Configuration
First, configure your ipx server with filesystem storage:
import { defineMiddleware } from 'astro:middleware'import { createipx, ipxFSStorage, createipxWebServer } from 'ipx'
const dir = process.env.ipx_IMAGES_DIR || new URL('../public/images', import.meta.url).pathname
// Create an ipx instance with filesystem storageconst ipx = createipx({ storage: ipxFSStorage({ dir }), maxAge: 604800, // 7 days})
// Create a web server for handling requestsconst server = createipxWebServer(ipx)
Handling Requests Using Web Standards
Your middleware should handle requests following standard web patterns:
export const onRequest = defineMiddleware(async ({ request }, next) => { const url = new URL(request.url)
if (url.pathname.startsWith('/_ipx')) { const newUrl = new URL(url.pathname.replace('/_ipx', ''), request.url) return server(new Request(newUrl)) }
return next()})
Astro’s Image Service Integration
By leveraging Astro’s built-in Image Service API, you can seamlessly integrate ipx, automatically translating standard image attributes into ipx parameters.
Creating a Custom Image Service
import type { ImageService, ImageTransform } from 'astro'
export class ipxImageService implements ImageService { getURL({ src, width, height, quality, format }: ImageTransform) { const params = [ width && `w_${width}`, height && `h_${height}`, quality && `q_${quality}`, format && `f_${format}`, ].filter(Boolean)
return `/_ipx/${params.join('/')}${src}` }
// parseURL and getHTMLAttributes implementations here}
☝️ Good To Know: Have a look at Astro’s built-in default image service for implementation details.
Configure Astro to use this service in astro.config.mjs
:
import { defineConfig } from 'astro/config'
export default defineConfig({ image: { service: { entrypoint: './src/utils/ipx-image-service.ts' }, },})
Utilizing Astro’s <Image>
Component
With the ipx image service configured, Astro’s <Image>
component automatically handles image optimization. Here’s how to use it for both basic and responsive images:
Basic Image Optimization
---import { Image } from 'astro:assets'import heroImage from '../assets/hero.jpg'---
<Image src={heroImage} width={800} height={600} quality={90} format="webp" alt="Optimized Hero Image"/>
This generates an ipx URL like:
/_ipx/w_800/h_600/q_90/f_webp/assets/hero.jpg
Responsive Images for Better Performance
The real power comes with responsive images that adapt to different screen sizes:
---import { Image } from 'astro:assets'import heroImage from '../assets/hero.jpg'---
<Image src={heroImage} widths={[400, 800, 1200, 1600]} sizes="(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 1200px" format="webp" quality={85} alt="Responsive Hero Image"/>
This creates multiple optimized variants, allowing browsers to choose the most appropriate size based on the device and viewport, significantly improving performance and user experience.
Conclusion
By combining ipx middleware with Astro’s Image Service API, you get a pretty sweet setup for handling images dynamically. It’s flexible, performs well, and makes the developer experience much smoother. Plus, sticking to web standards means everything just works together nicely - sometimes the simplest approach really is the best one.
Additional Resources
Here are some useful links to help you dive deeper into the topics covered:
- ipx Documentation - Complete guide to ipx features and configuration
- Astro Image Service API - Official documentation for creating custom image services
- Astro Middleware Guide - Learn more about Astro middleware patterns
- UnJS Ecosystem - Explore other Universal JavaScript utilities