Photo by cluttersnap on Unsplash
I personally don’t like Wordpress because, as soon as you try to build something a bit more complex, you have to add plugins and it quickly becomes expensive.
The same goes for the workload. It may automate a lot of the work initially, but again, as soon as it grows, you need a developer to keep up with all those plugins.
GatsbyJS to the rescue! But even though one of the main drivers of Gatsby’s development was to offer an alternative to Wordpress, migrating a blog is still not straightforward. There are some pitfalls along the way.
In this article you will see how I did, and the compromises I chose to make.
The final result looks like the website Mundo Século XXI, built by yours truly.
Wordpress allows the export of its content as an xml file. It is a good idea to convert that to markdown, so that we can use several Gatsby plugins that will do a lot of the heavy lifting for us.
Needless to say it here: unlike Wordpress, you don’t have to pay for Gatsby plugins.
There are a few good options to make this conversion. Exitwp, for example, is a good effort in Python.
I chose Wordpress export to markdown because it does a great job retrieving the images, and it was written in NodeJS.
In my case, the blog posts I exported from Wordpress were divided into categories and tags, which is quite common. Any converter I found retrieved that information. So I forked the Wordpress-export-to-markdown repository and modified it.
I sent my changes as a pull request to the original repository, but as of now it was not merged yet. So, in case you want use that converter with my code for categories and tags, you can use my fork.
This converter allows you to choose how to organize your markdown files. I suggest separating posts by year and month, prefixing each post with the date.
Once we have the posts in markdown format, it is time to play with Gatsby.
Here are the plugins I added:
gatsby-plugin-sharp
gatsby-transformer-sharp
gatsby-transformer-remark
gatsby-plugin-react-helmet
gatsby-source-filename: one config for each directory (blog and images)
gatsby-plugin-manifest
gatsby-plugin-less
gatsby-plugin-react-svg
module.exports = {
siteMetadata: {
title: `Wordpress to Gatsby`,
description: `Wordpress to Gatsby`,
author: `@Rodrigo_plp`,
},
plugins: [
`gatsby-plugin-sharp`,
`gatsby-transformer-sharp`,
{
resolve: 'gatsby-transformer-remark',
options: {
plugins: [
{
resolve: 'gatsby-remark-images',
options: {
maxWidth: 590
}
},
`gatsby-remark-copy-linked-files`
]
}
},
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `blog`,
path: `${__dirname}/src/blog`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
short_name: `starter`,
start_url: `/`,
background_color: `#663399`,
theme_color: `#663399`,
display: `minimal-ui`,
icon: `src/images/icon.png`
}
},
`gatsby-plugin-less`,
{
resolve: 'gatsby-plugin-react-svg',
options: {
rule: {
include: /\.inline\.svg$/
}
}
}
]
}
This is where you tell Gatsby about the structure of the directories you exported from Wordpress, and which information you want to retrieve from it.
For the purposes of building a blog, make use the two actions creatPages
and onCreateNodes
.
In onCreateNodes
there are two things you will want done: to retrieve the slugs of each post, and to create a timestamp for each of them.
The slugs will be used as a reference to build your pages.
The timestamp is to allow you to filter posts by date. Gatsby doesn’t do it, so this is to create a string from the date that can be easily consumed from the frontmatter.
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const value = createFilePath({ node, getNode })
createNodeField({
name: `slug`,
node,
value
})
const date = node.frontmatter.date
createNodeField({
name: 'timestamp',
node,
// convert date to unix timestamp & convert to number
value: +moment(date).format('X'),
})
}
}
In createPages
you will provide the path to your blog post template, you will give a GraphQL query to retrieve the necessary data from your nodes, and create handlers to control the previous and next posts.
exports.createPages = async ({ actions, graphql, reporter }) => {
const { createPage } = actions
const blogPostTemplate = path.resolve(`src/templates/blog-template.js`)
const result = await graphql(`
{
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___date] }
limit: 1000
) {
edges {
node {
fields {
slug
}
frontmatter {
title
date
categories
tags
}
}
}
}
}
`)
// Handle errors
if (result.errors) {
reporter.panicOnBuild(`Error while running GraphQL query.`)
return
}
const posts = result.data.allMarkdownRemark.edges
posts.forEach(({ node }, index) => {
const next = index === 0 ? false : posts[index - 1].node
const prev = index === posts.length - 1 ? false : posts[index + 1].node
createPage({
path: node.fields.slug,
component: blogPostTemplate,
context: {
slug: node.fields.slug,
prev,
next
},
})
})
}
You won’t use gatsby-browser.js
or gatbsy-ssr.js
on this setup. They can be deleted.
The main page is build in src/index.js
, and it holds a few challenges before accepting our Wordpress export.
The easiest part is harvesting the information available in the frontmatter. It takes a GraphQL query like this:
query MyQuery {
complete: allMarkdownRemark(sort: {order: DESC, fields: [frontmatter___date]}, limit: 4) {
edges {
node {
frontmatter {
title
date(
formatString: "MMMM DD, YYYY"
locale: "pt"
)
categories
coverImage
tags
summary
}
excerpt(pruneLength: 120)
fields {
slug
}
}
}
}
}
This query has a name, “complete”, that returns the 4 newest posts (set by limit: 4
on the second line), formats the date in Portuguese (with the super practical locale
flag) and prunes the length of the excerpt in 120 characters.
That’s a lot of bang for the buck!
You use the resulting data by allocating it into a variable (called “posts” in the example below) and mapping it:
const IndexPage = ({ data }) => {
const {
complete: { edges: posts }
} = data
return (
<div className='home'>
<div className='ultimas'>
{posts.map(({ node }) => {
return (
<div className='post-banner' key={node.frontmatter.title}>
<Link
style={{ boxShadow: `none` }}
to={node.fields.slug}
>
<article>
<header>
<div className='post-image'>
{node.frontmatter.coverImage === null ?
(<div className='stock-image'><Image /></div>) :
( // highlight-line
<div className='normal-image'> // highlight-line
<Banners props={node.frontmatter.coverImage} /> // highlight-line
</div> // highlight-line
) // highlight-line
}
</div>
<h3>{node.frontmatter.title}</h3>
</header>
<section>
<div className='post-summary'>
<p
dangerouslySetInnerHTML={{
__html: node.frontmatter.summary || node.excerpt,
}}
/>
</div>
</section>
</article>
</Link>
</div>
)
})}
</div>
</div>
)
}
Note the highlighted line in the code above. It brings up the issue with the images.
The remark plugin makes it very easy to display images inside blog posts. You just include a reference to their relative path and that is it. But retrieving one image per post to display on your front page is another story.
Some people approach this by configuring the heck out of the MDX plugin, others artificially include variables in static queries. I personally found those solutions added way too much complexity to the architecture.
Inspired by those solutions, I chose a compromise that requires manual work, but keeps things simple.
Gatsby already has a straight forward manner to include images in JavaScript pages, which is writing a component that uses gatsby-image and a static query to load it.
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
import Img from "gatsby-image"
const Image = () => {
const data = useStaticQuery(graphql`
query {
placeholderImage: file(relativePath: { eq: "msxxi.png" }) {
childImageSharp {
fluid(maxWidth: 300) {
...GatsbyImageSharpFluid
}
}
}
}
`)
return <Img fluid={data.placeholderImage.childImageSharp.fluid} />
}
export default Image
You specify the name of the image in the placeholderImage
field. Now, to do the same for all posts I use a routine that dynamically creates an image from the relativepath
of the node.
import React from "react"
import { StaticQuery, graphql } from "gatsby"
import Img from "gatsby-image"
const Banners = ({ props }) => {
return <StaticQuery
query={graphql`
query {
images: allFile(filter: { sourceInstanceName: { eq: "images" } }) {
edges {
node {
relativePath // highlight-line
childImageSharp {
fluid(maxWidth: 1000) {
...GatsbyImageSharpFluid
}
}
}
}
}
}
`}
render={(data) => { // highlight-line
const image = data.images.edges.find( // highlight-line
image => image.node.relativePath === props // highlight-line
) // highlight-line
if (image && image.node.childImageSharp !== null) { // highlight-line
return (<Img fluid={image.node.childImageSharp.fluid} />) // highlight-line
} else { // highlight-line
console.log('>>>>> path', props) // highlight-line
return null // highlight-line
} // highlight-line
}} // highlight-line
/>
}
export default Banners
In order for this to work, I have to manually copy the images from the posts to a src/images
folder. That is the compromise.
It works because, as you can see in the frontmatter section of this article above, I am displaying a limited amount of articles on the front page (4 articles). So I only have to copy 4 images. When I add new articles, I copy its image to that images folder.
Next, let’s see how to separate articles per category on the front page. Coming soon.
Questions? Hit me on the comments.
If you like GatsbyJS as I do, you may enjoy my tutorial Use Express and NGINX with a reverse proxy to add a backend to Gatsby.
May 06, 2020.