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 💻
- Make use of gatsbyimage 📷
- Post Inline images 📝
- Final Thoughts 🏁
- What's Next ➡️
Add Images to WordPress pages 💻
After adding images to the posts, let's also add some featured images to our pages.
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:
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 File
s. 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 thetypeName
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 theimageFile
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 useGatsbyImage
to handle it. - If we do have an image, but no
imageFile
, then it will default to using a normalimg
tag with thesourceUrl
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:
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.
Top comments (8)
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
andsource.mediaItemUrl?
If that WP specific?
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.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...
Thanks so much. Really grateful for the feedback and also happy it helps to realise your cool project about gardening 🎉
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 :)
Henrik, how high up were you on that slackline? That's insane!
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.
That's really impressive. The highest slack-line I've ever walked is about 1m off the ground in my backyard.
Respect!