Getting the right image handling and writing workflow
Posted on February 16, 2023
I’ve had a number of false starts when trying to get my personal site off the ground. None of the configurations, tools, or technology stacks I’ve used over the years ever felt quite right. What became apparent during my latest endeavor was the importance of having a good workflow—lowering the burden of writing new stuff and, as I’m digging more into photography, working with images.
TLDR: Just keep images inside the project. The simplest approach is often the best one.
In the beginning, I was using Wordpress. That’s mainly because there weren’t many other other options at the time. It was 2005, mind you. There was also a huge learning curve to developing locally. I wasn’t much of a coder back then either. Then there was writing, using FCKeditor was never really my thing. And don’t get me started the plugins!
In 2015, I migrated everything over to Jekyll. No more MAMP, no more PHP, no more developing a custom Wordpress theme. I loved the simplicity and performance of working on a static site generator. I still love it!
I enjoy writing in Markdown, too. But I’d need to write raw HTML if I wanted something not supported in Markdown, which took me out of my mental space for writing content as I attempted to remember how to code up my components.
Images were always a sore point for me. At some point, I wanted to tweak the build process in such a way that went beyond what Jekyll was able to do out of the box and looked to Grunt, and then finally, Gulp. I also started to use a lot more images in my posts. Manually compressing images for the web is tedious, so I found gulp plugin that handled compressing those images.
That is where I ran into my first wall. Essentially, I had two folders of images. One for the unprocessed images and another for the processed images I used for any of my posts. The conundrum was which folder to put under version control. It seem liked the uncompressed folder should be under version control, and the folder with processed images will get added at build time. That made sense. The problem was that the build process became really slow when it came to processing those images.
I punted on deciding what to do about that and migrated to 11ty. 11ty was much faster than Jekyll, and I was a huge fan of no longer needing to deal with ruby versions or a gemfile. But between having to continue to write HTML into my markdown files and dealing with a lagging build process, I lost interest yet again. I mean, I basically just migrated away from Jekyll and put the project back on a shelf.
A couple of years later, I picked my site back up and migrated it yet again, this time to Gatsby. Why? Mainly because I wanted to have a personal project where I can futz with React since I was using it often at work. That’s partially why I committed more to this effort. The other reason is because of the awfulness of Twitter and its red-pilled CEO, Space Karen, has got me rethinking why it’s in my best to have my own little space on the web.
With this renewed interest, I wanted to address the main reason why my every other effort fell flat: crappy workflow. Migrating to Gatsby forced my hand in ditching the Gulp scripts, which I realize I could have done at any time, but I didn’t. Once that was done, I was also able to run my local development in a single terminal pane instead of two.
Writing with MDX
As I said, writing raw HTML components in a markdown file is tedious. With Gatsby, I minimized some of that pain by using MDX, which supports JSX components inside of markdown. Even though I’m still putting some code in a big block of content, using JSX components means way less HTML writing, and an improved writing experience. That’s one win!
One thing I learned as I was using MDX is that much of the documentation and
graphql
Adventures in image hosting
Now that Gulp was gone, I needed to think about what I was going to do with all of my images. From my experience processing the images at build time, and its slowness, I wanted to try something different.
AWS and a custom subdomain
My first attempt at dealing with images was just to get them out of the project altogether and put them in an S3 bucket. It had two significant downsides: 1) separating images from the project, and 2) constant fear of doing something that will cost me $10,000. Regardless, I put those misgivings aside.
Once I set up my S3 bucket, I configured Serverless Image Handler. This tool processes images on the AWS Cloud with the ability to crop, apply filters, resize, and more. What it also did was convert images to WebP, reducing their file size significantly.
I thought that was really cool, except with images on an S3 bucket, I kinda have this ugly URL on my hands. I nitpick some things, and that was one of them. I set up a subdomain of
images.jaredcunha.com
How I did I do this? Literally just mashed at stuff until it worked.
And with that running, I was able to have the incredibly fast build process and images I can optimize however I needed. In the
.mdx
<img>
But then I began reading about
gatsby-plugin-image
gatsby-plugin-image
One of the cool things about gatsby-plugin-image is how it optimizes images for responsive design. It takes a single image and places them inside a
<picture>
srcset
dominantColor
What
gatsby-plugin-image
The plugin contains two seemingly identical components with crucial differences:
- can only accept a relative path to image that is plugged into the
<StaticImage>
propsrc
- can accept either a remote file path or variable that is plugged into the
<GatsbyImage>
prop.image
So this where things seemed like might they be straightforward but quickly grew complicated with using components inside content written with MDX.
Using gatsby-plugin-image with MDX
If you wanted to use
<GatsbyImage>
The tutorial that eventually got me on the right path was Paul Scanlon’s MDX Embedded Images with the All-New Gatsby Image Plugin, but not without some serious changes. I’m not going to do a full tutorial because it’s all moot by the time you’d get to the end of this post.
One change I needed to make was to the
createTypes
gatsby-node.js
The tutorial used something like this:
// mdxPage.jsx
<MDXProvider>
<MDXRenderer
remoteImages={embeddedImagesRemote}
localImages={embeddedImagesLocal}
/>
</MDXProvider>
---
title: Remote Images
embeddedImagesRemote:
- https://res.cloudinary.com/paulie.dev/image1.jpg
- https://res.cloudinary.com/paulie.dev/image2.jpg
---
import { getImage, GatsbyImage } from 'gatsby-plugin-image';
## Post Body
Lorem ipsum dolor...
## Remote Images
<GatsbyImage alt="green doggo" image={getImage(props.remoteImages[0])} />
<GatsbyImage alt="orange doggo" image={getImage(props.remoteImages[1])} />
Now, here's what worked for me. I didn't want to have to import components in the body of every post, as in Paul’s example, so I used also passed them through
<MDXProvider>
components
const PageTemplate = ({ data, children }) => {
const shortcodes = { Link, Image, PhotoGrid, PhotoGridItem }; // Provide common components here
...
return (
...
<MDXProvider components={shortcodes}>{children}</MDXProvider>
...
);
};
---
title: 'Closing out 2022'
embeddedImagesRemote:
- https://images.jaredcunha.com/image1.jpg
- https://images.jaredcunha.com/image2.jpg
---
## Post Body
Lorem ipsum dolor...
## Remote Images
<Image
src={props.data.mdx.embeddedImagesRemote[0]}
alt=""
/>
<Image
src={props.data.mdx.embeddedImagesRemote[1]}
alt=""
/>
Have a look at the full pull request if you’re interested, but just keep in mind that I scrapped much of it.
Why did I refactor my working code? AWS. They sent me a notice that there was suspicious activity on my S3 bucket and took out my lambda function. No function, no images. No images, no build. I suspect that it was all my tinkering with the serverless image handler that got their attention because I didn’t see anything in the logs that raised suspicion. But after a conversation with a co-worker convinced me that having an public S3 bucket is less than ideal, I looked at other ways to serve my images.
ImageKit
ImageKit is a hosting service that has the same image manipulation as Serverless Image Handler. It’s
sharp
With ImageKit, there were two major downsides. The first was that I was not able to use my custom domain unless I wanted to pay $50/month. The second was that bandwidth seemed like it would get very limited very fast on the free tier.
Because
gatsby-plugin-image
That's where it all connected and I realized the best option was in front of me the whole time. Just put the images in the project!
Using local images
This is what happens when you start going down rabbit holes, you simply cannot see outside your current path. I got so hung up on trying to figure out how wrangle this thing to optimize external images that I no longer thought to consider using local images.
gatsby-plugin-image
WebP
So with that, I was able to remove the
gatsby-node.js
createTypes
The JSX remains the same (edited for brevity):
const PageTemplate = ({ data, children }) => {
const shortcodes = { Link, Image, PhotoGrid, PhotoGridItem }; // Provide common components here
...
return (
...
<MDXProvider components={shortcodes}>{children}</MDXProvider>
...
);
};
And now the markdown is something like this:
---
title: 'Closing out 2022'
postImages:
- ../../images/image1.jpg
- ../../images/image2.jpg
---
## Post Body
Lorem ipsum dolor...
## Remote Images
<Image
src={props.data.mdx.frontmatter.postImages[0]}
alt=""
/>
<Image
src={props.data.mdx.frontmatter.postImages[1]}
alt=""
/>
Just get the images straight from the frontmatter and
gatsby-plugin-image