Handling images in my Astro blog
I recently ported my Gatsby blog over to Astro, and in the process had to resolve some finicky image issues relating to Astro and Netlify. This post covers a couple of those - namely resizing images and removing lazy loading in MDX files, using Netlify’s CI to optimise images locally, dealing with inline images and image captions.
Why I switched to Astro from Gatsby
I first started my programming blog in 2020 with Gatsby, and I really enjoyed it with how extensible it was. This year, my blog pivoted into covering my newfound hiking hobby, which turned out to be a lot of fun, and I quickly amassed over 50 posts and nearly 12s00 images about my hikes in Japan.
Unfortunately, my Gatsby blog wasn’t able to keep up with all the images and the build times ballooned. Once m builds started to time out on Netlify (which I use to host my site), I knew I needed a change. Hearing that Astro was the latest hotness in blogging platforms, I decided to make the leap over.
… Now I’m not going to say that it was easy, because it did take quite a while to do the port (although I did get sidetracked and did a bit of a site redesign in the process). But I can say that it was well worth the pain. The build times and general dev loop is so fast, and I can’t believe I dealt with that slow Gatsby setup for so long.
Dealing with images was the biggest pain point for me when porting my blog over, which makes sense, since images are such a big part of my blog now.
How to resize MDX images in Astro
I generally store images in my blog’s git repository at a width of 1920px, and then render them at 800px in my blog post (you can see an example of one of my hiking posts here).
Gatsby would automatically resize them to 800px for me in the build step (via gatsby-remark-images
). However Astro doesn’t do this if you use MDX (a fancier version of markdown) to render your images. Instead it would render them at 1920px on the page, and then the CSS would resize the image to only take up 800px on the page.
I wanted to get feature parity with Gatsby, so I went down the rabbit hole of figuring out how to get the same functionality in Astro.
Astro does technically support resizing images in the build step if you use their Image
component:
However this means you have to import each image individually, which means on average I would have to import 20 separate files at the start of each of my blog posts. The way you can do it normally in MDX:
Is so much shorter! And I really wanted to find a way to do this while maintaining the markdown way of rendering images.
Thanks to Brian Birtle’s post and a Github discussion about this, I landed on a solution that works for me. Basically, Astro has a SharpImageServiceConfig file in its repository, which you can swap out with your own version.
So I copy-pasted it and edited it to re-size images to have a max width of 1200px:
I then put the new image service config into my Astro’s config file:
The image-resizing gets applied to all the images I render on my site via MDX and using Astro’s <Image>
component. For any images rendered with the HTML <img>
tag, or an image that you have in your /public
folder that you are referencing in a CSS file, for example, still get excluded.
After I deployed this change, I found that resizing the image to 800px was a bit too blurry, so I settled on 1200px. It’s still not as crystal clear as it would be if I rendered the original 1920px image, but considering my images take half the time to download, I think it’s a good middle ground.
How to stop Astro images loading in lazily
Another image optimisation that Astro does is to have the images lazily load in by default. This means that they won’t load for the user until they scroll into view. If you are using the Image
component, and you wanted to turn it off, you can do so by setting the loading to “eager” mode:
However, if you are rendering images in an MDX file, you’ll need to make your own image service to do this:
It seems that lazy loading of images is not turned on for your local dev server. So to properly test that your changes work, you’ll have to test on your site’s production build.
Issues with Astro / Netlify caching
When Astro does its image optimisations, it caches them so you don’t have to do them on every build. This is a great timesaver, because it cuts down my build times from 11 minutes to only 2, but for some reason I found it really finicky and sometimes to remove the cache if I made a change to the image service code.
If I’m testing locally, I would restart my dev server and hard refresh the page (Ctrl + shift + R) to get the changes to update. But I couldn’t for the life of me get them to show up on my production build when I deployed it to Netlify. I’m not sure if I was doing something wrong but the “Clear cache and try site with latest branch commit” wasn’t actually clearing the chac.e
However if I went to the main Deploys page in the Netlify UI, and did a Trigger Deploy > Clear cache and deploy site, that seemed to work properly.
Building locally, and then deploying via Netlify CLI
If you don’t want these long build times when you need to re-optimise all your images (since it eats up your free Netlify build minutes), it turns out you can also build your changes locally, and have Netlify deploy that directly using Netlify’s CLI:
To properly kill the image cache, I removed the .netlify/.astro
and node_modules/.astro
folders, and then ran the Netlify build step.
However, the moment I switched from deploying locally back to using Netlify’s continuous deployment feature, it seems like it uses 2 different caches, so the continuous deployment feature would continue to use its last cache instead of the cache generated locally.
Rendering Astro images inside of React components
One cool thing with Astro is that you can write Astro pages using .astro
, and then render React (or any other frontend framework) components instead of them. However, this doesn’t work in the opposite direction, so you can’t render Astro images inside of React components. This means you can’t use Astro’s <Image />
component inside of React.
One workaround for this is to use Astro’s getImage function.
I had a use-case where I wanted to render all the cover images of my blog posts inside of a React post. In that case, I made an object mapping my blog post slugs to the Astro image:
And then I could render it inside of my React component like so:
Image blurring in Gatsby vs Astro
One neat feature that Gatsby provided that Astro doesn’t, is that Gatsby would provide a blurred version of an image before the full high-quality version loads in. This improves the perceived performance for the reader.
It seems like if you use Astro’s Picture component, it provides it. But since I didn’t want to have to import images and components into my MDX files, I didn’t go ahead with trying this.
Image filling in Gatsby vs Astro
Another neat feature that Gatsby had was you could have images have a “fill” or “cover” behaviour - basically even if images had different ratios, you could force them all to have the same height/width on the page by cropping out the edges that didn’t fit. Astro didn’t seem to support this either, (unless you specifically used their Picture
component).
Luckily, this problem was easily solved by some object-fit: cover;
CSS.
It seems as of the recent Astro 5 release, they do now have experimental support for image fill with their Image component.
Inline images in Astro
Even when I was using Gatsby, having inline images in my MDX blog posts was a bit hacky for me. Most of my blog posts have full-width images, so I don’t have to worry about this. But I have one edge case blog post (my YAMAP guide) where I only want the images to take up half of the screen.
I settled on just having some divs inside of my MDX that wrap the image and the text, and make sure that they are inlined. I also have some logic to have the images alternate between left and right using float: right
.
Similarly I have a books round-up page where I wanted to add little book covers next to the reviews. This one is a lot more simpler since I didn’t have to worry about floating images left and right, and I put the width of the image in the MDX file itself instead of letting the CSS handle it:
Adding image captions to Astro
Images in Markdown can have captions - and these can be separate to the alt text that is shown for screen reader users.
With my hiking posts, I’d been using the caption feature for a lot of my images.
I was a bit worried at first, because none of the solutions I could find would support actual caption text - most of them tend to just re-use the alt text as the caption below the image. I didn’t really want to go through 50 of my posts and update all the formatting! But luckily, I did stumble upon remark-figure-caption which is working well for me at the moment.
Debugging issues with nvm and sharp
Another point I struggled with was getting errors around importing sharp, with errors like Rollup failed to resolve import "sharp"
. In the end, this turned out to be that my nvm version was too old, and following the instructions at the bottom of this Github thread fixed it for me.
As one final note, when searching for solutions to these issues, as well as generally Googling, I found the Astro Discord community to be quite a useful source of information, and these don’t show up in a Google search. So I would recommend taking a look (or asking your question there) if you get stuck.