Continuing the works of setting up this Hugo blog, after initial hosting setup and some styling and theming updates (for which all updates are shared in the publicly forked repo). This time, I was looking at image processing and delivery.
It seems kind of obvious, but with a static site generator, easy image processing can be a bit tricky. Hugo offers several built in image processing capabilities, like resizing, crop, fit, fill, and other manipulators like filtering, colorizing, and blurring. However, they all have to be used knowingly, like so:
{{ $image.Resize "600x webp picture" }}
$image
is processed to be resized (using the Resize
function with the arguments 600px
width, webp
as format, and picture
as WebP preset used in cwebp
). This is all nice and good, but way too complicated. As a user, I don’t really want to think about that.
A solution is to outsource this and other image processing logic to a third-party image processing service like TwicPics, Cloudimage, or imagekit (full disclosure: my employer acquired TwicPics recently). Let’s integrate TwicPics and see how it behaves.
What TwicPics does and how it works
TwicPics doesn’t need much configuration. The setup guide is pretty straightforward. You have to set up a custom subdomain, for me this was https://rebeblog.twic.pics
. This domain is then configured to a source path, which is https://rebe.blog/
for me as it want to allow all images served on my blog to be optimized and delivered via TwicPics.
The TwicPics URL can now be used to access my publicly served images, like so:
https://rebeblog.twic.pics/uploads/Screenshot_2024-06-07_at_22.44.08.png?twic=v1/max=1400
Note the query parameter twic=v1/max=1400
. This is part of the transformations API, where you can pipe transformations applied to an image. Besides standard things like cropping, rotation, resizing, etc., we can also do things like correct colors for color blind people by just adding achromatopsia=0.5
or deuteranopia=...
respectively. TwicPics also allows for background removal or replacement (ML detecting subjects) and it works with focal points, again recognizing spots of attention via AI/ML.
When the image is processed, it gets delivered via their CDN behind twic.pics
.
Integrating TwicPics into Hugo
The integration into a Hugo site is pretty easy.
First, we need to add the following into our head
tag of the website:
<script async defer src="https://rebeblog.twic.pics/?v1"></script>
Second, we need to render the image tags as follows:
{{ $image := .Destination | safeURL }}
{{ $path := "" }}
{{ if eq .Page.BundleType "leaf" }}
{{ $path = .Page.RelPermalink }}
{{ end }}
{{ if and hugo.IsProduction site.Params.twicPicsUrls }}
<img loading="lazy" alt="{{ .Text }}" data-twic-src="image:{{ $path }}{{ $image }}" />
{{ else }}
<img loading="lazy" alt="{{ .Text }}" src="{{ $path }}{{ $image }}" />
{{ end }}
For leaf bundles we need to add the relative permalink of the page so the files are resolved correctly when added relative to the bundle.
E.g., sunrise.png
is placed in /content/posts/good-morning/
and used in the index.html
with ![The sun rises](sunrise.png)
. The src
or data-twic-src
attribute will be rendered as /posts/good-morning/sunrise.png
.
However, pages (markdown files without a separate directory) will render the file relative to the static
directory.
The src
attribute is replaced by data-twic-src
and the URL is prefixed by image:
. And .Text
is the image description.
I’ve hacked that together in the typo template (and fixed it afterward), feel free to have a look there. Additionally, I bound it to Hugo’s IsProduction
state as local files aren’t accessible by TwicPics and the parameter twicPicsUrls
to configure this easily or turn it off if needed.
Thus, my hugo.toml
looks as such:
[params]
# TwicPics
twicPicsUrls = "https://rebeblog.twic.pics/?v1"
# ...other parameters
That’s it, actually.
Impressive Improvements
TwicPics recognizes the device, viewport size, heck, even the bandwidth that is available, and renders the images in the backend accordingly to produce an optimal user experience.
Let’s have a look at what it does with Brave/Chrome and the picture from a previous post:
Noteworthy:
- We see that the image is served via the TwicPics domain and not mine.
- Although the original is a PNG, we get the content type
image/webp
, thus it’s a WebP. - The width of the column in the template is 700px, so TwicPics renders the image at the max size of 1400px.
- The size of the delivered file is 94 KB. The original is 1.6 MB. That’s 17 times smaller!
Needless to say that this is quite an improvement!
And best, it’s free for small use cases like this blog, or whatever fits into 3 GB of CDN traffic.
Have fun! 🏞️