📸 Unpic, Image Hosting Services and Self‑Hosting Images #
I put together this Deno Fresh responsive images post to talk about an idea I had when I first heard about Matt Kane’s new unpic tool. The framework which first set the standard for a modern image plugin was probably Gatsby, and Matt Kane was one of the engineers that worked on their plugin. The idea behind unpic is to provide a simple API for including responsive images in your web apps. It is designed with image hosting services in mind. A kind of one tool to rule them all as there are components for Astro, React, Preact and Svelte, and each works with the popular image hosting services.
One complaint I often hear about image hosting services relates to cost. In fact, I got hit with a surprise image hosting bill for one side project I was working on. Since then, I have favoured self-hosting images for side projects. My idea was to use the convenient API which unpic provides with self-hosting. This saves you writing a lot of client code for handling responsive images. The image files can be included in the front end project or in a separate serverless API app.
Proof of Concept #
I got something working. It’s more of a proof of concept than a final working solution. Because I wanted the resizing API to be serverless and run on different hosting services, I opted for using Rust WASM for doing the image resizing heavy lifting. That is all working, though I am still looking at a solution for generating images in next-gen formats like AVIF and WebP. In this post I take you through what I have so far and some ideas for the missing part.
🧱 Imgix Image API #
I mentioned earlier that unpic supports multiple image hosting services. For using unpic it makes sense to have our own self-hosted image resizing API mimic an existing one. The Imgix Rendering API seems a popular choice and is used by Prismic, Sanity and other tools, so that is what I opted for.
In the rest of this post, I share some code from a Deno Fresh app I built as a proof of concept.
It uses a Rust WASM module for the actual resizing. We skip over the Rust code here. Under the
hood, we use the image
crate for resizing, in case you are interested.
You can see the Rust code in the GitHub repo (link further down).
Client Frontend Markup #
Deno Fresh uses Preact client markup. However, there are React and Svelte plugins which use the same API and will work in exactly the same way if you prefer a different framework.
The most interesting parts here are:
-
We import unpic
Image
component in line1
. It replaces the HTMLimg
in our markup. -
We will see below that
unpic
manipulates thesrc
value. To output the correct code, I needed to use an absolute URL, even though a relative one would be fine here if we were using animg
tag. -
The
src
value is actually an API route. Here it is available within the same project, though there is nothing stopping you from separating out the API route into its own serverless app.
🖥️ Generated Image Markup #
This is the HTML which unpic generated for the first image:
This has most lot of the attributes Addy Osmani recommends you set on your images for a good user experience. You might notice we are missing source sets for next-gen images. In fact, there is a way we can feature detect if a browser supports next-gen formats. I will get to this later!
Before we get into that, though, some important features here are:
-
aspect-ratio
is set: this helps to reduce Cumulative Layout Shift -
loading="lazy"
: this is a good default, and Chrome, Firefox and Safari all support lazy loading now -
srcset
: the responsive part which provides hints to the browser, so a mobile device should not end up wasting data downloading an image bigger than it needs
Notice these features come out of the box with unpic. You would have to remember to include them
manually if you were working with a vanilla img
tag.
Image Resize API Route #
The generated image source URLs follow this pattern:
The pathname includes a filename for the image we need. Then the URL search parameters provide the
width and height values which you would expect. As well as those, we have fit=min
a scaling/resizing parameter. Finally, auto=format
indicates the
API should use content negotiation to determine the most suitable image format (returning a next-gen format when supported).
Using Deno Fresh, we can create a template API route. This will listen on the routes matching http://localhost:8000/api/images/[FILENAME]
. Astro, Remix and SvelteKit have mechanisms for handling template parameters. With Deno Fresh,
we can create a handler file at routes/api/images/[file].ts
to listen
on these routes.
Here we are just reading the image parameters from the request URL.
Finding the Requested Image #
Next, we can map the request pathname to an image file within the project. Since the file
parameter in the pathname can be arbitrary, it is important to check there is actually a matching
file within the project and to return a 404
error where there is not
one.
Deno Fresh Responsive Images: Resizing #
The resizing is done with WASM code. The JavaScript sharp library is an alternative here, though I decided to use WASM, so the code could be deployed in more environments. sharp is based on the libvips C library and there is a related wasm-vips project. wasm-vips is still in the early stages of development though.
The import in the first line is where we make the WASM module accessible to the handler.
Deno Fresh Responsive Images: Response #
Finally, we can return the image with caching headers. When returning a 404
error, set the headers not to cache, just in case there is a temporary issue.
🦀 Rust WASM Module #
The wasmbuild
Deno module makes creating WASM code in Deno fairly straightforward.
It is a wrapper for wasm-pack
and you can just use that directly if
not working in Deno. In Deno however, just add wasmbuild
to your deno.json
:
Then initialize the project and once your Rust code is ready, build the WASM module:
🐘 Deno Fresh Responsive Images: What is Missing? #
I mentioned earlier that there is no next-gen image generation here yet. We can implement our own
form of content negotiation by inspecting the Accept
header on the
request:
A typical header value here would be:
That is for the current version of Firefox, letting us know we can send AVIF or WebP images in this case. We can then request the preferred format from the WASM module, so it churns out a next-gen image when supported.
What is missing is the WASM code to output the next-gen image. For WebP there are a few Rust
implementations which port the C libwep implementation. Similarly, for AVIF, you can find ports of
the C rav1e library. These are not ideal for writing WASM modules in Rust, and pure Rust
implementations are favoured. For AVIF, the ravif
library is a potential
pure Rust option. I still need to do some feasibility work here.
🗳 Poll #
🙌🏽 Deno Fresh Responsive Images Wrapping Up #
We saw some thoughts on working with Deno Fresh responsive images in Deno. In particular, you saw:
-
how you can generate responsive image markup using
unpic
; - how you might implement content negotiation for delivering the optimal image; and
- the building blocks for an approach to a self-hosted image API
The complete code for this project (including Rust source) is in the Rodney Lab GitHub repo . I do hope the post has either helped you in an existing project or provided some inspiration for a new project.
Get in touch if you have some questions about this content or suggestions for improvements. You can add a comment below if you prefer. Also, reach out with ideas for fresh content on Deno or other topics!
🏁 Deno Fresh Responsive Images: Summary #
Is there a Deno Fresh image plugin? #
- At the time of writing, there is no image plugin for Deno Fresh. That said, the unpic plugin, although not written specifically for Deno Fresh, can do a lot of heavy lifting. We have seen there is a Preact package for the plugin which will be useful when you host your images with a service. We also saw you can use it to save you writing boilerplate code when self-hosting images.
Does unpic only work with image hosting services? #
- unpic is image tooling designed to make adding responsive images to your web apps easier when working with an image hosting service. The plugin just generates an img tag with image source URLs in a format compatible with an image hosting service API (like Imgix). You can create your own self-hosted API which mirrors the Imgix API URLs, for example. This can be convenient for spinning up a quick side-project or proof-of-concept, as well as offer cost savings.
How does content negotiation work for serving next-gen images? #
- Image formats like AVIF and WebP promise equivalent image quality to JPEG or PNG, but with smaller files. They are fantastic for improving user experience. However, older browser versions do not support them. Content negotiation lets you determine which formats a particular user browser supports and from those provided to serve the optimal one. In the code for an image route handler, you can look for the `Accept` request header. This might look something like `image/avif,image/webp,*/*`. In that case, the browser is signalling is supports AVIF. As this is the newest and most efficient format, we can respond with an AVIF image, remembering to set the response Content-Type header to `image/avif`.
🙏🏽 Deno Fresh Responsive Images: Feedback #
Have you found the post useful? Would you prefer to see posts on another topic instead? Get in touch with ideas for new posts. Also, if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, then please consider supporting me through Buy me a Coffee.
Just dropped a post on how you can use unpic to power your own self-hosted image API with resizing.
— Rodney (@askRodney) March 10, 2023
Hope you find it useful!
#unpicturethishttps://t.co/BpRMKmH73M pic.twitter.com/2i2mSYUhAo
Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on Twitter, @rodney@toot.community on Mastodon and also the #rodney Element Matrix room. Also, see further ways to get in touch with Rodney Lab. I post regularly on Astro as well as Deno. Also, subscribe to the newsletter to keep up-to-date with our latest projects.