Building A Photo Blog on Strapi Gatsby And Typescript: Part 3 Frontend

Taylor Nodell
4 min readDec 9, 2020

--

Now that I got my images onto Strapi with their corresponding iNaturalist data in part 2, I’m ready to fix up the frontend.

With my images being served, I noticed they weren’t quite as pretty as I remember. Comparing to the image I have on my hard drive, the difference is pretty clear. The first thing I did was adjust my Gatsby config to increase default JPG quality.

Before JPG quality increase on left, notice the chunks. White square to highlight compression

I still wasn’t satisfied by upping the JPEG default quality, especially in some of the noisier images. It’s a website about photos, so the image quality needs to be a priority. For fluid images it looks like increasing the maxWidth and explicitly setting the quality in GraphQL increases image quality. I set my maxWidth to 4000 as that’s the size of the width of my portrait photos, and the quality to 90. This isn’t the maximum resolution possible, but I will deliver the full resolution on the Photo’s specific page.

childImageSharp {
fluid(maxWidth: 4000, quality: 90) {
...GatsbyImageSharpFluid
}

Just for fun, I wanted to see what my image looked like when I dropped the quality to 10. Bizarrely, my build time increased when dropping the quality to 10. I assume this has something to do Gatsby having to do more work to compress an image.

Blue Mink Picture at image quality 10
Chunky

The next issue was how to handle portrait vs landscape orientation. At the moment I’ve only got two photo sizes (4000x6000), portrait and landscape. For landscape oriented photos my quick “programmer design” scales well from desktop to mobile. But I need to adjust the layout for portrait oriented pictures to avoid the entire page being swallowed up by the photo, since Gatsby fluid <Img> fills the width of the container.

I came up with an aspect ratio check in my component, with separate styles for the portrait and landscape photos.

const Photo: React.FC<PhotoProps> = ({ data }) => {
const photo = data.strapiPhoto
const aspectRatio = photo.image.imageFile.childImageSharp.fluid.aspectRatio
const isPortraitOrientation = aspectRatio < 1
return (
<LayoutRoot>
{isPortraitOrientation ? (
<div className={styles.portraitPhotoContainer}>
<div className={styles.portraitRowContainer}>
<div className={classnames(styles.imageContainer, styles.portraitImageContainer)}>
<Img fluid={photo.image.imageFile.childImageSharp.fluid} />
</div>
<div className={styles.portraitTextContainer}>
<PhotoDesc photoData={photo} />
</div>
</div>
</div>
) : (
<div className={styles.photoContainer}>
<div className={classnames(styles.imageContainer)}>
<Img fluid={photo.image.imageFile.childImageSharp.fluid} />
</div>
<PhotoDesc photoData={photo} />
</div>
)}
</LayoutRoot>
)
}

I added some quick breakpoints in my CSS to handle when a portrait photo is being displayed. Clamp the max width at 550px (still allowing fluid to do its thing when scaling below that width), and changing the text flow to be below the image when the device is less than 900px wide.

/* Portrait */
@media screen and (min-width: 600px) {
.portraitImageContainer {
max-width: 550px;
}
}
@media screen and (max-width: 900px) {
.portraitRowContainer {
flex-direction: column;
align-items: center;
}
.portraitTextContainer {
padding-left: 0;
}
}

With the photos looking tolerable now, its time to redo the <Seo> component.

Desktop and mobile views of a portrait image of kangaroos
Mobile vs Desktop views

I swapped react-helmet for async-react-helmet, getting rid of an annoying warning and hopefully future proofing my app a bit. I spent too much time trying to figure out that in Gatsby all you need to do is npm i react-helmet-async gatsby-plugin-react-helmet-async, include gatsby-plugin-react-helmet-async, and swap out my import statements. The plugin provides the <HelmetProvider> context high up the component hierarchy. I had been unnecessarily trying to add it in my <LayoutRoot> component.

Then I created a <PhotoSeo> component to update my meta tags to be related to the picture being shown. This goes at the top of my <Photo> component after the<LayoutRoot>

const PhotoSeo: React.FC<PhotoSeoProps> = ({ seoProps }) => (
<Helmet
meta={[
{
name: `description`,
content: seoProps.description
},
{
name: `keywords`,
content: seoProps.keywords.join(' ')
},
{
property: `og:title`,
content: seoProps.title
},
{
property: `og:description`,
content: seoProps.description
}
]}
></Helmet>
)

I needed a quick “About” page. Here I realized an issue with fluid Gatsby images. I popped in a little avatar photo and wanted to have it scale with screen size. However, placing flex on the direct parent of a Gatsby <Img>, causes it disappear because <Img> is trying to fill the width of the container and a flex container’s width is determined by its content’s width. To combat this I placed a min and max width on a new containing div for the <Img>. I also added a media query for screen size to resize on mobile like I do for the Photo component.

Then I was unable to get my image and text block to stay centered in the desktop view. I had a parent flex container row, with two children with flex-grow: 1 to fill the container. I figured this has something to do with the fluid nature of the Gatsby img. My solution was just to add an empty div to “balance” out the container, which was treating the container as only having one child width to account for.

It is wild to think that it is still a struggle to center an image with modern web tools, and that using an empty div is still a solution. Oh well. Time to learn how to deploy this thing.

Github Repo

--

--

Taylor Nodell

Developer. Musician. Naturalist. Traveler. In any order. @tayloredtotaylor