Chris Biscardi

moon indicating dark mode
sun indicating light mode

SEO Images with Unsplash

I added an SEO component to my blog recently and on top of that I decided to set up infrastructure to pull in images for Twitter, etc. The SEO component already handled titles and descriptions for cards on Twitter, but I wanted something a bit more… interesting. I like large images and I eventually want to start drawing images for my blog posts. So I dove into gatsby-transformer-sharp.

The sharp transformer makes it absolutely trivial to handle an image described in frontmatter and post it as a Twitter meta card.

First I jacked the SEO component from the Gatsby starters. Then I added a couple key components. Notably twitter:image and the switch that enabled the summary_large_image card type.

import React from "react";
import PropTypes from "prop-types";
import Helmet from "react-helmet";
import { StaticQuery, graphql } from "gatsby";
function SEO({
description,
lang = "en",
meta = [],
keywords = [],
title,
image
}) {
return (
<StaticQuery
query={detailsQuery}
render={data => {
const metaDescription =
description || data.site.siteMetadata.description;
return (
<Helmet
htmlAttributes={{
lang
}}
title={title}
titleTemplate={`%s | ${
data.site.siteMetadata.title
}`}
meta={[
{
name: `description`,
content: metaDescription
},
{
property: `og:title`,
content: title
},
{
property: `og:description`,
content: metaDescription
},
{
property: `og:type`,
content: `website`
},
{
property: `twitter:site`,
content: "@chrisbiscardi"
},
{
name: `twitter:card`,
content: !!image
? `summary_large_image`
: `summary`
},
{
name: `twitter:creator`,
content: data.site.siteMetadata.author
},
{
name: `twitter:title`,
content: title
},
{
name: `twitter:description`,
content: metaDescription
}
]
.concat(
keywords.length > 0
? {
name: `keywords`,
content: keywords.join(`, `)
}
: []
)
.concat(
image
? {
name: `twitter:image`,
content: `https://www.christopherbiscardi.com${image}`
}
: []
)
.concat(meta)}
/>
);
}}
/>
);
}
SEO.propTypes = {
description: PropTypes.string,
lang: PropTypes.string,
meta: PropTypes.array,
keywords: PropTypes.arrayOf(PropTypes.string),
title: PropTypes.string.isRequired
};
export default SEO;
const detailsQuery = graphql`
query DefaultSEOQuery {
site {
siteMetadata {
title
description
author
}
}
}
`;

Then I stuck an image in my frontmatter for a blog post. The addition of gatsby-transformer-sharp enabled the following query to get the processed image.

export const pageQuery = graphql`
query($id: String!) {
mdx(id: { eq: $id }) {
id
code {
body
}
excerpt
frontmatter {
title
featuredImage {
childImageSharp {
fixed {
src
}
}
}
}
}
}
`;

Then it’s a matter of using that query and passing it into the SEO component.

const imageRoot = data.mdx.frontmatter.featuredImage;
let src = undefined;
if (imageRoot) {
src = imageRoot.childImageSharp.fixed.src;
}
return (
<SiteLayout
sidebar={<aside css={{ minWidth: "200px" }}>some stuff</aside>}
>
<SEO
description={data.mdx.excerpt}
title={data.mdx.frontmatter.title}
image={src}
/>

Now to add a new “featured image” that shows up on Twitter, we only need to add a pointer to the image in our frontmatter.

Downsides

I currently use Netlify CMS to edit my blog posts and I’m starting to feel the need for something a bit more custom. Currently, Netlify CMS will blow away the featuredImage key on updates, erasing the Twitter opengraph image from being presented.