DEV Community

Cover image for How to handle Images and make use of gatsby-image - Guide to Gatsby WordPress Starter Advanced with Previews, i18n and more
Henrik Wirth for NeverNull

Posted on • Edited on

How to handle Images and make use of gatsby-image - Guide to Gatsby WordPress Starter Advanced with Previews, i18n and more

After the previous part we have a blog ready with featured images. Now, how do we get these images to be local and do some more magic with them? Gatsby comes with a super helpful plugin called gatsby-image for image processing at build time.

In this part I will show you, how you can make use of gatsby-images superpowers and deliver your images in a static fashion.

Table of Contents

Add Images to WordPress pages 💻

After adding images to the posts, let's also add some featured images to our pages.

Add Featured Image

Now run gatsby develop and checkout http://localhost:8000/___graphql.

Run:

query GETPAGES {
  wpgraphql {
    pages {
      nodes {
        title
        uri
        featuredImage {
          sourceUrl
        }
      }
    }
  }
}

You should see something like this:

GraphQL Image Output

As you can see we get an absolute path to the featured images with sourceUrl. What we want instead though, is a local image, with the abilities to use gatsby-image's filters.

I already wrote an article about this, but for the sake of integrity of the tutorial series, I gonna add the information here too.

Make use of gatsby-image 📷

We gonna make use of Gatsby's abilities to add custom resolvers, to customize the schema, so that WordPress images get handled as local Files. Gatsby will then automatically treat the images as files, that are getting processed by gatsby-image.

1.) Add Resolver to Gatsby

// gatsby-node.js

const { createRemoteFileNode } = require(`gatsby-source-filesystem`)

exports.createResolvers = async (
  {
    actions,
    cache,
    createNodeId,
    createResolvers,
    store,
    reporter,
  },
) => {
  const { createNode } = actions

  await createResolvers({
    WPGraphQL_MediaItem: {
      imageFile: {
        type: "File",
        async resolve(source) {
          let sourceUrl = source.sourceUrl

          if (source.mediaItemUrl !== undefined) {
            sourceUrl = source.mediaItemUrl
          }

          return await createRemoteFileNode({
            url: encodeURI(sourceUrl),
            store,
            cache,
            createNode,
            createNodeId,
            reporter,
          })
        },
      },
    },
  })
}
  • WPGraphQL_MediaItem: This depends on your config. It starts with the typeName of your gatsby-source-graphql.
  • createRemoteFileNode gives you the ability to pull in remote files and automatically adds them to your schema.
  • imageFile: will be the type you can query (see below).
  • type: 'File': will add the MediaItems as Files, which is great, because now gatsby-image can make use of it.
  • url: encodeURI(sourceUrl): Here we encode the Url coming from WordPress, to make sure it is parsed correctly even if the image paths include umlauts.

2.) Add fluid Image fragment

As in the previous part. We will add some fragments, to our queries. First let's add a fragment.js inside our template folder like this:

// src/templates/fragments.js

const FluidImageFragment = `
    fragment GatsbyImageSharpFluid_tracedSVG on ImageSharpFluid {
        tracedSVG
        aspectRatio
        src
        srcSet
        sizes
    }
`

module.exports.FluidImageFragment = FluidImageFragment
  • This fragment will serve for our fluid images with traced SVG abilities.

3.) Add fragment to createPages.js and createPosts.js

We need to add these fragments to our page and post queries.

Update createPages.js

// create/createPages.js

const pageTemplate = require.resolve("../src/templates/page/index.js")

const {FluidImageFragment} = require("../src/templates/fragments")
const {PageTemplateFragment} = require("../src/templates/page/data")

const GET_PAGES = `
    ${FluidImageFragment}
    ${PageTemplateFragment}

    query GET_PAGES($first:Int $after:String) {
        wpgraphql {
            pages(
                first: $first
                after: $after
                # This will make sure to only get the parent nodes and no children
                where: {
                    parent: null
                }
            ) {
                pageInfo {
                    hasNextPage
                    endCursor
                }
                nodes {                
                  ...PageTemplateFragment
                }
            }
        }
    }
`

// This file has more content. Check the repo for the rest of the code.
  • As you can see we add our FluidImageFragment and then parse it inside our query string.
  • Also new is the PageTemplateFragment. We will add that fragment now.

Add PageTemplateFragment

In the last tutorial part we already added the posts data.js. Now we also do the same for pages with the addition of our image fragment.

// src/templates/page/data.js

const PageTemplateFragment = `
    fragment PageTemplateFragment on WPGraphQL_Page {
        id
        title
        pageId
        content
        uri
        isFrontPage
        featuredImage {
            sourceUrl
            altText
            imageFile {
                childImageSharp {
                    fluid(maxHeight: 400, maxWidth: 800, quality: 90, cropFocus: CENTER) {
                        ...GatsbyImageSharpFluid_tracedSVG
                    }
                }
            }
        }
    }
`

module.exports.PageTemplateFragment = PageTemplateFragment
  • featuredImage now has the imageFile field added, thats coming form our gatsby-image implementation with the resolver.
  • We can make use of our GatsbyImageSharpFluid_tracedSVG fragment, as we added it before.

Note: Usually gatsby-image would add these fragments already, but not in our build scripts. If you would only use the fragments inside page or static queries, you would already have these fragments available.

Update createPosts.js

// create/createPosts.js

const {
  PostTemplateFragment,
  BlogPreviewFragment,
} = require("../src/templates/post/data.js")

const {FluidImageFragment} = require("../src/templates/fragments")

const { blogURI } = require("../globals")

const postTemplate = require.resolve("../src/templates/post/index.js")
const blogTemplate = require.resolve("../src/templates/post/blog.js")

const GET_POSTS = `
    # Here we make use of the imported fragments which are referenced above
    ${FluidImageFragment}
    ${PostTemplateFragment}
    ${BlogPreviewFragment}

    query GET_POSTS($first:Int $after:String) {
        wpgraphql {
            posts(
                first: $first
                after: $after
                # This will make sure to only get the parent nodes and no children
                where: {
                    parent: null
                }
            ) {
                pageInfo {
                    hasNextPage
                    endCursor
                }
                nodes {           
                    uri     

                    # This is the fragment used for the Post Template
                    ...PostTemplateFragment

                    #This is the fragment used for the blog preview on archive pages
                    ...BlogPreviewFragment
                }
            }
        }
    }
`

// This file has more content. Check the repo for the rest of the code.
  • Same as above. We add the FluidImageFragment to our query.

Update PostTemplateFragment and BlogPreviewFragment

// src/templates/post/data.js

const PostTemplateFragment = `
    fragment PostTemplateFragment on WPGraphQL_Post {
        id
        postId
        title
        content
        link
        featuredImage {
            sourceUrl
            altText
            imageFile {
                childImageSharp {
                    fluid(maxHeight: 400, maxWidth: 800, quality: 90, cropFocus: CENTER) {
                        ...GatsbyImageSharpFluid_tracedSVG
                    }
                }
            }
        }
        categories {
            nodes {
                name
                slug
                id
            }
        }
        tags {
            nodes {
                slug
                name
                id
            }
        }
        author {
            name
            slug
        }
    }
`

const BlogPreviewFragment = `
    fragment BlogPreviewFragment on WPGraphQL_Post {
        id
        postId
        title
        uri
        date
        slug
        excerpt
        content
        featuredImage {
            sourceUrl
            altText
            imageFile {
                childImageSharp {
                    fluid(maxHeight: 400, maxWidth: 800, quality: 90, cropFocus: CENTER) {
                        ...GatsbyImageSharpFluid_tracedSVG
                    }
                }
            }
        }
        author {
            name
            slug
        }
    }
`

module.exports.PostTemplateFragment = PostTemplateFragment
module.exports.BlogPreviewFragment = BlogPreviewFragment
  • You might notice, that there is potential of abstraction here. For simplicity I'll leave it like this for now.

4.) Update Image.js to FluidImage.js

Now we will need to update our image component. For now we are only using fluid images and therefore we gonna rename our Image.js to FluidImage.js and update it like so:

// src/components/FluidImage.js

const FluidImage = ({ image, withFallback = false, ...props }) => {
  const data = useStaticQuery(graphql`
      query {
          fallbackImage: file(relativePath: { eq: "fallback.svg" }) {
              publicURL
          }
      }
  `)

  /**
   * Return fallback Image, if no Image is given.
   */
  if (!image) {
    return withFallback ? <img src={data.fallBackImage.publicURL} alt={"Fallback"} {...props}/> : null
  }

  if (image && image.imageFile) {
    return <GatsbyImage fluid={image.imageFile.childImageSharp.fluid} alt={image.altText} {...props}/>
  }

  return <img src={image.sourceUrl} alt={image.altText} {...props}/>
}

export default FluidImage
  • If now image, we show a fallBackImage or render no element at all.
  • If we do have an image and it has the imageFile field, we know it has been processed by our resolver and therfore we can use GatsbyImage to handle it.
  • If we do have an image, but no imageFile, then it will default to using a normal img tag with the sourceUrl as source.

This will guarantee us, that if for some reason, we are using dynamic queries and don't have the preprocessing abilities, that we will still have a valid output of the images. This will come in handy, if we are creating previews later on.

5.) Update page, blog and post template

// src/templates/page/index.js

import React from "react"

import Layout from "../../components/Layout"
import SEO from "../../components/SEO"
import FluidImage from "../../components/FluidImage"


const Page = ({ pageContext }) => {
  const {
    page: { title, content, featuredImage },
  } = pageContext

  return (
    <Layout>
      <SEO title={title}/>

      <FluidImage image={featuredImage} style={{ marginBottom: "15px" }}/>

      <h1> {title} </h1>
      <div dangerouslySetInnerHTML={{ __html: content }}/>
    </Layout>
  )
}

export default Page
// src/templates/post/blog.js

import React from "react"
import Layout from "../../components/Layout"
import PostEntry from "../../components/PostEntry"
import Pagination from "../../components/Pagination"
import SEO from "../../components/SEO"

const Blog = ({ pageContext }) => {
  const { nodes, pageNumber, hasNextPage, itemsPerPage, allPosts } = pageContext

  return (
    <Layout>
      <SEO
        title="Blog"
        description="Blog posts"
        keywords={[`blog`]}
      />

      {nodes && nodes.map(post => <PostEntry key={post.postId} post={post}/>)}

      <Pagination
        pageNumber={pageNumber}
        hasNextPage={hasNextPage}
        allPosts={allPosts}
        itemsPerPage={itemsPerPage}
      />
    </Layout>
  )
}

export default Blog
// src/templates/post/index.js

import React from "react"

import Layout from "../../components/Layout"
import SEO from "../../components/SEO"
import FluidImage from "../../components/FluidImage"


const Post = ({ pageContext }) => {
  const {
    post: { title, content, featuredImage },
  } = pageContext

  return (
    <Layout>
      <SEO title={title}/>

      <FluidImage image={featuredImage} style={{ marginBottom: "15px" }}/>

      <h1> {title} </h1>
      <div dangerouslySetInnerHTML={{ __html: content }}/>
    </Layout>
  )
}

export default Post

Post Inline images 📝

One thing I wanted to mention shortly, that there is a nice plugin, that can help you to get inline images as local static images. So for example you are using the normal Gutenberg in your posts, then you want images, that you included in the post also to be downloaded at build time and stored locally.

I won't implement this here, as I won't rely on Gutenberg and rather use Advanced Custom Fields - Flexible Content field in the upcoming parts.

But checkout gatsby-wpgraphql-inline-images if you need this functionality.

Final Thoughts 🏁

Checkout how the site looks now:

Guide to Gatsby WordPress Starter Advanced - Screenshot

PS: That's actually me walking on a highline (slackline) in this picture 😱. Such a fun sport.

With this part I would say, we have a very solid base for everything that is coming next. The basic functionalities of a WordPress site are implemented and we can now start think about how we dynamically design our content.

Find the code base here: https://github.com/henrikwirth/gatsby-starter-wordpress-advanced/tree/tutorial/part-6

What's Next ➡️

We'll dive into Advanced Custom Fields Flexible Content field and create a page-builder like experience with it. This will open us the doors, to dynamically create UI elements filled with content.

Part 7 - PageBuilder with ACF Flexible Content

Top comments (8)

Collapse
 
marcillien profile image
Marc Illien • Edited

I was looking for such sample code to work with the custom resolvers! Not for WP but it helped a lot. Thank you!

Out of interest: What is the reason for the custom check between source.sourceUrl and source.mediaItemUrl?

let sourceUrl = source.sourceUrl
if (source.mediaItemUrl !== undefined) {
      sourceUrl = source.mediaItemUrl
}

If that WP specific?

Collapse
 
henrikwirth profile image
Henrik Wirth

Yup that is WordPress specific. Some mediaItems would have the Url in the mediaItemSourceUrl rather that the sourceUrl. But depending on your API, I would just log source and see what you got. Then you can figure out what field you need to source from.

Collapse
 
susanlangenes profile image
Susan Langenes

Henrik, this is so awesome! I am really grateful to you for this tutorial! I've followed it up to this point; haven't added the ACF part because the blog I'm using as a source doesn't have ACF fields but I'm going to go through it again with a site that does. Here's what I've got so far: susanlangenes.github.io/gatsby-pla...

Collapse
 
henrikwirth profile image
Henrik Wirth

Thanks so much. Really grateful for the feedback and also happy it helps to realise your cool project about gardening 🎉

Collapse
 
susanlangenes profile image
Susan Langenes

Aw thanks!
Next is to figure out how to make a blog sidebar with links to a list of recent posts, and categories and date archives :)

Collapse
 
ibjorn profile image
Björn Potgieter

Henrik, how high up were you on that slackline? That's insane!

Collapse
 
henrikwirth profile image
Henrik Wirth

Probably about 40m high on a 60m slackline. The view on this spot is amazing though. It reaches all the way down to the valley. So it feels like you are much higher.

Collapse
 
ibjorn profile image
Björn Potgieter

That's really impressive. The highest slack-line I've ever walked is about 1m off the ground in my backyard.
Respect!