Home >Web Front-end >JS Tutorial >How to Build a Developer Blog with Gatsby and MDX
You can easily publish your ideas to sites like Dev.to, Hashnode or Medium, but the ideal is to have full control over your own content. There’s an ever-growing list of tools for building your own website and controlling your own content. In this extensive tutorial, I’ll be covering how you can make your content shine using Gatsby, with the added bells and whistles you get with such an ecosystem.
I originally used Jekyll to publish my blog, but then switched to Gatsby, using the Lumen template. I’ve been using Gatsby since version 0, around May 2017.
I’ll be going from a Hello, World! Gatsby project through to a coding blog with code syntax highlighting and a theme toggle for that dark mode goodness.
There’s a rich ecosystem of plugins, starters and themes available for Gatsby to get you up and running quickly, but I want to take a progressive disclosure approach to presenting Gatsby, focusing on the basics of how a Gatsby project works.
Gatsby is a static site generator, so there’s no dynamic generation of pages when the pages are requested. The built output for a Gatsby site can be hosted on a CDN, making it globally available and super scalable.
Gatsby can use Markdown files to create pages in a site project. Gatsby will read the Markdown files into the Gatsby file system and transform the Markdown to HTML and then when building the site create static pages.
The end result is a super fast site with little latency when requesting the pages.
I’ve been documenting my development journey since 2016 in Markdown. Markdown offers a way to enable simple editing in plain text files that can be converted to HTML.
MDX (or Markdown JSX) is a tool that lets you write JSX in your Markdown documents, sort of like this:
<span>import <span>{ RainbowText }</span> from './components/rainbow'; </span>## <span>A Markdown Heading </span><span><span><span><RainbowText</span>></span>Wheeeeeeee<span><span></RainbowText</span>></span> </span>
Gatsby is by far the best framework I’ve used for working with Markdown and MDX, as the there’s no special notation needed above using frontmatter on your posts.
If you’re going to follow along, there’s a few things you’ll need:
If you don’t have any of these, there’s both StackBlitz and GitHub Codespaces where you can create an empty GitHub repository and get started with a development environment from there.
I’ll be using VS Code as my text editor and Yarn as my preferred package manager in the examples below. If you prefer npm, that’s cool. ?
You can also find the complete code for this tutorial on GitHub.
Okay, it’s time to get started!
It’s time to spin up a Gatsby project. I’m going to do the majority of this from the command line to begin with:
<span>import <span>{ RainbowText }</span> from './components/rainbow'; </span>## <span>A Markdown Heading </span><span><span><span><RainbowText</span>></span>Wheeeeeeee<span><span></RainbowText</span>></span> </span>
Cool. Now, before going anywhere else with this, I’m going to need to add a .gitignore file before installing any npm modules:
<span># create the project directory </span><span>mkdir my-gatsby-blog </span><span># change into the directory </span><span>cd my-gatsby-blog </span><span># initialise a package.json file </span><span>yarn init -y </span><span># initialise the git repo </span><span>git init </span>
Now I can install all the npm goodness I need to without VS Code Git screaming at me about too many active changes. Let’s now install some dependencies to get up and running with Gatsby:
<span># create .gitignore file in my directory </span><span>touch .gitignore </span><span># add ignore contents with echo </span><span>echo "# Project dependencies </span><span>.cache </span><span>node_modules </span><span> </span><span># Build directory </span><span>public </span><span> </span><span># Other </span><span>.DS_Store </span><span>yarn-error.log" > .gitignore </span>
Next, we’ll add the first React component (of many) for the project. I’ll add the following to the index.js file I created:
<span>yarn add gatsby react react-dom </span><span># -p is to create parent directories too if needed </span><span>mkdir -p src/pages </span><span># create the index (home) page </span><span>touch src/pages/index.js </span>
I’m now ready to run the Gatsby develop command from the command line:
<span>import <span>React</span> from "react"; </span> <span>export default function <span>IndexPage</span>() { </span> <span>return <span><span><h1</span>></span>Hello, World!<span><span></h1</span>></span>; </span><span>} </span>
This will spin up the Gatsby dev sever and say that my project is available to view in the browser on port 8000 (the default Gatsby port). The URL is http://localhost:8000/.
Using the Gatsby binary commands directly from the command-line interface (CLI) is totally doable, but most people will add the available commands to the scripts section on the package.json file, like this:
<span># if you're using npm ? </span><span># $(npm bin)/gatsby develop </span><span>yarn gatsby develop </span>
As an added bonus, there’s a few extras that can be added to the Gatsby scripts here.
If we don’t want to run the project on the same port each time, it can be changed with the -p flag, and and a port specified after that. For example, gatsby develop -p 8945.
If we want to open the browser tab once the project is ready, we can add -o to the script.
I’ll do the same with the serve script, so I know when I’ve built a project it’s on a different port to the development one:
<span>"scripts": { </span> <span>"build": "gatsby build", </span> <span>"dev": "gatsby develop", </span> <span>"serve": "gatsby serve", </span> <span>"clean": "gatsby clean" </span><span>}, </span>
And with that, the mandatory “Hello, World!” welcome is complete and I can move on with the rest of this post! ?
Lastly I’ll commit the changes I’ve made so far:
<span>"scripts": { </span> <span>"build": "gatsby build", </span> <span>"dev": "gatsby develop -p 8945 -o", </span> <span>"serve": "gatsby serve -p 9854 -o", </span> <span>"clean": "gatsby clean" </span><span>}, </span>
Okay, there’s not a great deal going on with the project right now, so first up I’ll add in some content, from the command line again:
<span># add everything for committing </span><span>git add . </span><span># commit to repo </span><span>git commit -m 'init project' </span>
I’ll be using these throughout the examples I’m making.
You’ll notice the file extension .mdx. This is an MDX file.
Before I add some content for the blog, I’ll need to talk about front matter.
Front matter is a way to store information about the file that can be used by Gatsby when building the pages from them. For now, I’ll add a title of the post and a date. I’ll also add some content to them. Here’s our first post:
<span># this creates the folders in the root of the project </span><span>mkdir -p content/2021/03/{06/hello-world,07/second-post,08/third-post} </span><span># create individual files </span><span>touch content/2021/03/06/hello-world/index.mdx </span><span>touch content/2021/03/07/second-post/index.mdx </span><span>touch content/2021/03/08/third-post/index.mdx </span>
Here’s our second post:
<span><span>--- </span></span><span><span><span>title: Hello World - from mdx! </span></span></span><span><span><span>date: 2021-03-06</span> </span></span><span><span>---</span> </span> My first post!! <span><span>## h2 Heading</span> </span> Some meaningful prose <span><span>### h3 Heading</span> </span> Some other meaningful prose
A third post:
<span><span>--- </span></span><span><span><span>title: Second Post! </span></span></span><span><span><span>date: 2021-03-07</span> </span></span><span><span>---</span> </span> This is my second post!
That’s it for the posts for now, because these posts aren’t yet recognized by Gatsby as pages. I’ll need to let Gatsby know where to find content to add to the project. To do this, I’m going to add a configuration file to Gatsby.
Let’s commit the changes I’ve made to Git:
<span>import <span>{ RainbowText }</span> from './components/rainbow'; </span>## <span>A Markdown Heading </span><span><span><span><RainbowText</span>></span>Wheeeeeeee<span><span></RainbowText</span>></span> </span>
Gatsby config is what’s used to define and configure the many Gatsby plugins you can use. More on the Gatsby plugin eco system in a bit. For now, I’m going to create the file, again in the terminal:
<span># create the project directory </span><span>mkdir my-gatsby-blog </span><span># change into the directory </span><span>cd my-gatsby-blog </span><span># initialise a package.json file </span><span>yarn init -y </span><span># initialise the git repo </span><span>git init </span>
This creates the gatsby-config.js at the root of the project so I can start configuring Gatsby to read the .mdx files I created earlier.
Now I can install and configure the plugins Gatsby needs to source and display the files I created. I’ll install them all now and briefly detail what they’re for:
<span># create .gitignore file in my directory </span><span>touch .gitignore </span><span># add ignore contents with echo </span><span>echo "# Project dependencies </span><span>.cache </span><span>node_modules </span><span> </span><span># Build directory </span><span>public </span><span> </span><span># Other </span><span>.DS_Store </span><span>yarn-error.log" > .gitignore </span>
A quick look at the package.json now shows that I have the following dependency version installed:
<span>yarn add gatsby react react-dom </span><span># -p is to create parent directories too if needed </span><span>mkdir -p src/pages </span><span># create the index (home) page </span><span>touch src/pages/index.js </span>
One thing to note is that, in Gatsby, there’s no need to import React in your components with React 17. But for the sake of completeness, and to avoid any confusion, I’ll be including it in these examples.
Now I need to configure gatsby-plugin-mdx and gatsby-plugin-mdx. In the gatsby-config.js file, I’ll add this:
<span>import <span>React</span> from "react"; </span> <span>export default function <span>IndexPage</span>() { </span> <span>return <span><span><h1</span>></span>Hello, World!<span><span></h1</span>></span>; </span><span>} </span>
Commit changes up to now:
<span># if you're using npm ? </span><span># $(npm bin)/gatsby develop </span><span>yarn gatsby develop </span>
Now it’s time to see where I’m at with the files in Gatsby by using the Gatsby GraphQL client, GraphiQL. You may have noticed, if you’re following along, that the CLI indicates two URL locations to view the project:
<span>"scripts": { </span> <span>"build": "gatsby build", </span> <span>"dev": "gatsby develop", </span> <span>"serve": "gatsby serve", </span> <span>"clean": "gatsby clean" </span><span>}, </span>
I’m going to be using the ___graphql (three underscores) route now to see the files in the file system.
If this seems a bit intimidating, I’ll attempt to cover all the parts that may not seem to make much sense. If you’re following along, you should be fine copying the examples into the GraphiQL explorer.
When I open up the GraphiQL explorer, I have several Explorer panels. This is all available data to explore in the project and is dependent on what I’ve configured in the gatsby-config.js file.
The GraphiQL query panel and the results are next to that. This is where I’ll be writing GraphQL queries to retrieve the data I need. There’s also a QUERY VARIABLES section at the bottom of the query panel, and I’ll come onto that later on.
Over on the far right is the GraphQL Documentation Explorer. Because of GraphQL’s strict typing, this means that it’s able to generate its own documentation on its data. But that’s outside the scope of this post.
Next, I’m going to query for the files I added earlier in the GraphiQL query panel. In this query, I’m querying the title and date defined in the font matter of the files:
<span>"scripts": { </span> <span>"build": "gatsby build", </span> <span>"dev": "gatsby develop -p 8945 -o", </span> <span>"serve": "gatsby serve -p 9854 -o", </span> <span>"clean": "gatsby clean" </span><span>}, </span>
If we pop that into the query panel press the big play button, we get back some data in the results panel. We can also use the Explorer in the left panel to pick out the data. Here’s what I get after running the query:
<span>import <span>{ RainbowText }</span> from './components/rainbow'; </span>## <span>A Markdown Heading </span><span><span><span><RainbowText</span>></span>Wheeeeeeee<span><span></RainbowText</span>></span> </span>
This is a big JSON object with the relevant information we requested in the query. We’ll look at how to use this soon. For now, this means that we can use this data in the Gatsby project to make pages.
In the gatsby-config.js file, there’s also an option to specify site metadata. Site metadata is for when I want to reuse common data like the site title and description.
This is will be useful further down the road when I want to add meta tags to the site for search engine optimization (SEO). (Again, more on that later.) For now, I’m going to define some basic information about the site in the gatsby-config.js with the siteMetadata object.
I could define the site metada directly in the module.exports like so:
<span># create the project directory </span><span>mkdir my-gatsby-blog </span><span># change into the directory </span><span>cd my-gatsby-blog </span><span># initialise a package.json file </span><span>yarn init -y </span><span># initialise the git repo </span><span>git init </span>
The site metadata object can get a bit large, and I’ve found keeping it in its own object can make it a bit simpler to reason about, so instead I’m going to define it separately:
<span># create .gitignore file in my directory </span><span>touch .gitignore </span><span># add ignore contents with echo </span><span>echo "# Project dependencies </span><span>.cache </span><span>node_modules </span><span> </span><span># Build directory </span><span>public </span><span> </span><span># Other </span><span>.DS_Store </span><span>yarn-error.log" > .gitignore </span>
Then add the siteMetadata object to the Gatsby config file:
<span>yarn add gatsby react react-dom </span><span># -p is to create parent directories too if needed </span><span>mkdir -p src/pages </span><span># create the index (home) page </span><span>touch src/pages/index.js </span>
Now I can hop over to the GraphiQL explorer again and query that site metadata with the following query:
<span>import <span>React</span> from "react"; </span> <span>export default function <span>IndexPage</span>() { </span> <span>return <span><span><h1</span>></span>Hello, World!<span><span></h1</span>></span>; </span><span>} </span>
It’s always a good idea to stop and restart the development server if you’re making changes to the gatsby-config.js file, so I’ll do that (Ctrl c, then yarn develop), then in the GraphiQL explorer refresh the page and run the query again to get the data back:
<span># if you're using npm ? </span><span># $(npm bin)/gatsby develop </span><span>yarn gatsby develop </span>
Now that I have the site metadata in the Gatsby file system, I can query it wherever I want to use it with the Gatsby static query hook useStaticQuery. I’m going to kill off the dev server and restart after I’ve added the following to the src/pages/index.js file:
<span>"scripts": { </span> <span>"build": "gatsby build", </span> <span>"dev": "gatsby develop", </span> <span>"serve": "gatsby serve", </span> <span>"clean": "gatsby clean" </span><span>}, </span>
A quick note on some of the notation there: const { site: { siteMetadata }, } is quick way to get to the data in the site query, where I’m pulling the siteMetadata from the site object. This is referred to as destructuring.
Now, after I’ve started the dev server again, I can go over to the browser console (Control Shift J in Windows/Linux, Command Option J on macOS) and see the siteMetadata object in the console output.
I get the following console output:
<span>"scripts": { </span> <span>"build": "gatsby build", </span> <span>"dev": "gatsby develop -p 8945 -o", </span> <span>"serve": "gatsby serve -p 9854 -o", </span> <span>"clean": "gatsby clean" </span><span>}, </span>
Don’t worry about the console warning for a missing 404 page not found (net::ERR_ABORTED 404 (Not Found)). I’ll make that later.
To avoid having to write this query each time, I want to use it in a component. I’m going to abstract this out into its own hook:
<span># add everything for committing </span><span>git add . </span><span># commit to repo </span><span>git commit -m 'init project' </span>
Now I’ll add in a hook to the newly created src/hooks/use-site-metadata.js file to get the site metadata on demand:
<span># this creates the folders in the root of the project </span><span>mkdir -p content/2021/03/{06/hello-world,07/second-post,08/third-post} </span><span># create individual files </span><span>touch content/2021/03/06/hello-world/index.mdx </span><span>touch content/2021/03/07/second-post/index.mdx </span><span>touch content/2021/03/08/third-post/index.mdx </span>
You may have noticed that this query isn’t the same as the one from from the GraphiQL explorer:
<span>import <span>{ RainbowText }</span> from './components/rainbow'; </span>## <span>A Markdown Heading </span><span><span><span><RainbowText</span>></span>Wheeeeeeee<span><span></RainbowText</span>></span> </span>
This is to name the query. Because I’ll be using a lot of queries in the project, it makes sense to give them meaningful names.
Now I’ll implement the new hook into the src/pages/index.js file:
<span># create the project directory </span><span>mkdir my-gatsby-blog </span><span># change into the directory </span><span>cd my-gatsby-blog </span><span># initialise a package.json file </span><span>yarn init -y </span><span># initialise the git repo </span><span>git init </span>
That’s a lot less verbose, and I’m able to pick and choose what items I want from the SITE_METADATA_QUERY.
It’s time to commint the changes made so far:
<span># create .gitignore file in my directory </span><span>touch .gitignore </span><span># add ignore contents with echo </span><span>echo "# Project dependencies </span><span>.cache </span><span>node_modules </span><span> </span><span># Build directory </span><span>public </span><span> </span><span># Other </span><span>.DS_Store </span><span>yarn-error.log" > .gitignore </span>
To style this project, I’m going to be using Theme UI, because of its speed with implementing layouts and features like dark mode. I’ll be detailing what’s relevant to what I’m doing and reasons for that, although this won’t be a guide on how to use Theme UI.
There’s a few additional dependencies to add for Theme UI, which are:
<span>yarn add gatsby react react-dom </span><span># -p is to create parent directories too if needed </span><span>mkdir -p src/pages </span><span># create the index (home) page </span><span>touch src/pages/index.js </span>
With those installed, I’ll need to add the gatsby-plugin-theme-ui to the gatsby-config.js plugin array:
<span>import <span>React</span> from "react"; </span> <span>export default function <span>IndexPage</span>() { </span> <span>return <span><span><h1</span>></span>Hello, World!<span><span></h1</span>></span>; </span><span>} </span>
Now, if I stop and restart the dev server I have a slightly different looking site! It’s all gone a bit blue — or periwinkle, to be precise! This is the gatsby-plugin-theme-ui doing its thing and that color is the default.
The Gatsby plugin for Theme UI offers a lot of configuration options, some of which I’ll cover in more detail when needed. For now, I’m going to create a folder and define a theme object for Theme UI to use:
<span># if you're using npm ? </span><span># $(npm bin)/gatsby develop </span><span>yarn gatsby develop </span>
In the src/gatsby-plugin-theme-ui/index.js file, I’m going to add in a couple of the Theme UI presets, define the theme object, and spread in the swiss preset to the theme, to the theme colors, and to the styles.
For dark mode, I’m using the deep Theme UI preset and spreading that into the modes object for dark. (More on this soon.) For now, know that this is going to take care of a lot of the theming for me:
<span>"scripts": { </span> <span>"build": "gatsby build", </span> <span>"dev": "gatsby develop", </span> <span>"serve": "gatsby serve", </span> <span>"clean": "gatsby clean" </span><span>}, </span>
Now if I restart the dev server (again, yes, you’ll learn to deal with it) it will look a bit more acceptable with the Swiss theme being applied. At the time of writing, Theme UI sometimes doesn’t refresh the localhost page, so it’s necessary to do a browser page refresh.
Commit the changes so far to Git:
<span>"scripts": { </span> <span>"build": "gatsby build", </span> <span>"dev": "gatsby develop -p 8945 -o", </span> <span>"serve": "gatsby serve -p 9854 -o", </span> <span>"clean": "gatsby clean" </span><span>}, </span>
Time to add some React components!
Gatsby doesn’t have a specific layout, giving that responsibility to the developer. In this case, I’m making a layout for the whole site. It’s possible to incorporate many layouts for use in a Gatsby project, but for this example I’ll be using just one.
Now I’m going to refactor what I have currently so that everything is wrapped by a Layout component. What I have currently in src/pages/index.js can be used for a Header component, so I’m going to make a couple of files now for Layout and Header:
<span># add everything for committing </span><span>git add . </span><span># commit to repo </span><span>git commit -m 'init project' </span>
Now to move the title and description from src/pages/index.js to the newly created src/components/header.js component.
Rather than have the useSiteMetadata used in the Header component, I’ll pass the useSiteMetadata props I need to the header from the Layout component, which is where the header is going to live. (More on that shortly.) First up, here’s the header component, which lives in src/components/header.js:
<span>import <span>{ RainbowText }</span> from './components/rainbow'; </span>## <span>A Markdown Heading </span><span><span><span><RainbowText</span>></span>Wheeeeeeee<span><span></RainbowText</span>></span> </span>
I’ve added in some basic styles using the Theme UI layout elements. This looks a bit different from before: Box, Link, Heading … what? These are all Theme UI components that can be used for layouts, form elements and more.
You may notice the as={GatsbyLink} link prop added to the Link component. This uses the as prop in Theme UI and lets the component being passed in take on Theme UI styles.
There’s a great post from Paul Scanlon explaining in more detail how this is done in Theme UI. For a really comprehensive explanation of Theme UI, there’s also “Understanding Theme UI” by the same author.
There’s also the sx and variant props from Theme UI. sx enables additional styles to be passed to the component. Think of it as an equivalent to the JSX style={{}} prop. The variant prop allows a group of predefined styles to be applied from the theme to the component being used.
Now for the Layout component, which is located in src/components/layout.js:
<span># create the project directory </span><span>mkdir my-gatsby-blog </span><span># change into the directory </span><span>cd my-gatsby-blog </span><span># initialise a package.json file </span><span>yarn init -y </span><span># initialise the git repo </span><span>git init </span>
Here I’m keeping the useSiteMetadata hook and passing the props the Header component needs, again with the sx prop to add some basic styles for alignment to the main containing div. Then I’m creating a main wrapper for the children.
The children prop is to return anything the Layout component encapsulates, which will include anything I want to apply the layout to. For example:
<span># create .gitignore file in my directory </span><span>touch .gitignore </span><span># add ignore contents with echo </span><span>echo "# Project dependencies </span><span>.cache </span><span>node_modules </span><span> </span><span># Build directory </span><span>public </span><span> </span><span># Other </span><span>.DS_Store </span><span>yarn-error.log" > .gitignore </span>
This will return everything in the Layout component and what it’s wrapping. In in the example above, that will currently be the header and the H1 wrapped by the Layout component.
As an example, I’ll go back to the index page (src/pages.index.js) and add the following:
<span>yarn add gatsby react react-dom </span><span># -p is to create parent directories too if needed </span><span>mkdir -p src/pages </span><span># create the index (home) page </span><span>touch src/pages/index.js </span>
The result is the header, provided in the Layout component and the H1 This is wrapped.
Now it’s time to get the posts I created at the beginning and display them on the index page as a list of clickable links.
To get the post information, I’ll recreate the query I made in the section on querying local files with GraphQL with a couple of extra bits:
<span>import <span>React</span> from "react"; </span> <span>export default function <span>IndexPage</span>() { </span> <span>return <span><span><h1</span>></span>Hello, World!<span><span></h1</span>></span>; </span><span>} </span>
I’ve added in the id of the node and the slug. This is the file path to the .mdx files.
The excerpt is using a Gatsby function to get the first 250 characters from the post body, also adding some formatting to the date with another built-in Gatsby function.
Then as a way to order the posts in date descending order, I’ve added a sort: allMdx(sort: { fields: [frontmatter___date], order: DESC }) {. This is sorting on the date in the posts front matter.
Adding that to the GraphiQL explorer gives me this result:
<span>import <span>{ RainbowText }</span> from './components/rainbow'; </span>## <span>A Markdown Heading </span><span><span><span><RainbowText</span>></span>Wheeeeeeee<span><span></RainbowText</span>></span> </span>
Now I can use that query in the src/pages/index.js file to get that data for use in the index page. In the IndexPage function, I’ll destructure data from the props given to the component via the GraphQL query:
<span># create the project directory </span><span>mkdir my-gatsby-blog </span><span># change into the directory </span><span>cd my-gatsby-blog </span><span># initialise a package.json file </span><span>yarn init -y </span><span># initialise the git repo </span><span>git init </span>
This uses the components previously detailed. Note that the excerpt, frontmatter, and slug are being destructured from data.allMdx.nodes:
<span># create .gitignore file in my directory </span><span>touch .gitignore </span><span># add ignore contents with echo </span><span>echo "# Project dependencies </span><span>.cache </span><span>node_modules </span><span> </span><span># Build directory </span><span>public </span><span> </span><span># Other </span><span>.DS_Store </span><span>yarn-error.log" > .gitignore </span>
Clicking on the links will take me to the Gatsby.js development 404 page. That’s because I haven’t made the pages for the .mxd files yet. That’s next.
I’ll commit what I’ve done so far before moving on:
<span>yarn add gatsby react react-dom </span><span># -p is to create parent directories too if needed </span><span>mkdir -p src/pages </span><span># create the index (home) page </span><span>touch src/pages/index.js </span>
I’m going to be using the Gatsby File System Route API to get the file paths of the posts I created earlier on. The File System Route API is a way to programmatically create pages from my GraphQL data.
This approach has a special file notation for the page that’s going to be targeted when Gatsby generates the file system data at build time. The file indicates the node and the slug. I’ll create the file first, then detail where the data is coming from:
<span>import <span>React</span> from "react"; </span> <span>export default function <span>IndexPage</span>() { </span> <span>return <span><span><h1</span>></span>Hello, World!<span><span></h1</span>></span>; </span><span>} </span>
In the file, I’ll define a GraphQL query for the data I want to include in this template:
<span># if you're using npm ? </span><span># $(npm bin)/gatsby develop </span><span>yarn gatsby develop </span>
Now that’s a lot of code, so I’ll break it down. It’s mainly to do with the GraphQL query:
<span>"scripts": { </span> <span>"build": "gatsby build", </span> <span>"dev": "gatsby develop", </span> <span>"serve": "gatsby serve", </span> <span>"clean": "gatsby clean" </span><span>}, </span>
The start of the query is taking in a slug with POST_BY_SLUG($slug: String), and the main node is mdx, so I’m using mdx.slug like the filename {mdx.slug}.js.
If I take that query and paste it into my GraphiQL explorer and press the play button, I get this:
<span>"scripts": { </span> <span>"build": "gatsby build", </span> <span>"dev": "gatsby develop -p 8945 -o", </span> <span>"serve": "gatsby serve -p 9854 -o", </span> <span>"clean": "gatsby clean" </span><span>}, </span>
That’s because there’s no variable defined for $slug in the GraphiQL explorer. If you look to the bottom of the query panel, you’ll see there’s a Query Variables section. Clicking this will expand it. In here is where I need to add a variable for slug. I’ll define it in curly braces with the path of one of the files:
<span># add everything for committing </span><span>git add . </span><span># commit to repo </span><span>git commit -m 'init project' </span>
Running the query again, I’ll get all the data for that file. I’ve commented out the body output for readability:
<span># this creates the folders in the root of the project </span><span>mkdir -p content/2021/03/{06/hello-world,07/second-post,08/third-post} </span><span># create individual files </span><span>touch content/2021/03/06/hello-world/index.mdx </span><span>touch content/2021/03/07/second-post/index.mdx </span><span>touch content/2021/03/08/third-post/index.mdx </span>
What the File System Route API is doing is passing the individual file paths into the page query in src/pages/{mdx.slug}.js and returning the data to the page from that query in the ({ data }) prop being passed to the page.
In this file, you may notice I’ve destructured the body from the data being returned, and then title from from the frontmatter, in a two-level destructure:
<span><span>--- </span></span><span><span><span>title: Hello World - from mdx! </span></span></span><span><span><span>date: 2021-03-06</span> </span></span><span><span>---</span> </span> My first post!! <span><span>## h2 Heading</span> </span> Some meaningful prose <span><span>### h3 Heading</span> </span> Some other meaningful prose
An alternative way to do it would be:
<span><span>--- </span></span><span><span><span>title: Second Post! </span></span></span><span><span><span>date: 2021-03-07</span> </span></span><span><span>---</span> </span> This is my second post!
Using destructuring makes it a lot less verbose.
One last thing to note is the MDXRenderer wrapping the body of the post. This is everything included in the .mdx file after the front matter block. The compiled MDX from the GraphiQL query, which was commented out, is what needs to be wrapped in the MDXRenderer:
<span>import <span>{ RainbowText }</span> from './components/rainbow'; </span>## <span>A Markdown Heading </span><span><span><span><RainbowText</span>></span>Wheeeeeeee<span><span></RainbowText</span>></span> </span>
I’ll commit the changes now:
<span># create the project directory </span><span>mkdir my-gatsby-blog </span><span># change into the directory </span><span>cd my-gatsby-blog </span><span># initialise a package.json file </span><span>yarn init -y </span><span># initialise the git repo </span><span>git init </span>
Now clicking on one of the links on the index page will take me to the desired .mdx page, but it looks a bit different from the index page, right?
That’s because there’s no layout wrapping it yet. This is where I can use the Gatsby browser API and use the wrapPageElement function to wrap all the page elements. It’s also recommended that I use the same function in Gatsby SSR.
To avoid duplicating the same code in two files, I’ll create a third file with the actual code I’m going to use and import that into the two gatsby-* files mentioned.
First up, I’ll create the files needed:
<span># create .gitignore file in my directory </span><span>touch .gitignore </span><span># add ignore contents with echo </span><span>echo "# Project dependencies </span><span>.cache </span><span>node_modules </span><span> </span><span># Build directory </span><span>public </span><span> </span><span># Other </span><span>.DS_Store </span><span>yarn-error.log" > .gitignore </span>
The root wrapper file is where I’ll be using the wrapPageElement function:
<span>yarn add gatsby react react-dom </span><span># -p is to create parent directories too if needed </span><span>mkdir -p src/pages </span><span># create the index (home) page </span><span>touch src/pages/index.js </span>
Then, in both the gatsby-browser.js and gatsby-ssr.js files, I’ll add this:
<span>import <span>React</span> from "react"; </span> <span>export default function <span>IndexPage</span>() { </span> <span>return <span><span><h1</span>></span>Hello, World!<span><span></h1</span>></span>; </span><span>} </span>
If there are any changes needed to the wrapPageElement function, I can do it in the one file root-wrapper.js.
Time to stop and restart the dev server again to see the changes take effect!
Because the layout component is being used here to wrap all the page elements on the site, there’s no need to keep it on the index page anymore, so I’m going to remove that from src/pages/index.js:
<span># if you're using npm ? </span><span># $(npm bin)/gatsby develop </span><span>yarn gatsby develop </span>
I’ll commit the changes so far before moving on:
<span>"scripts": { </span> <span>"build": "gatsby build", </span> <span>"dev": "gatsby develop", </span> <span>"serve": "gatsby serve", </span> <span>"clean": "gatsby clean" </span><span>}, </span>
Time to make that 404 page!
<span>"scripts": { </span> <span>"build": "gatsby build", </span> <span>"dev": "gatsby develop -p 8945 -o", </span> <span>"serve": "gatsby serve -p 9854 -o", </span> <span>"clean": "gatsby clean" </span><span>}, </span>
In the src/pages/404.js file, I’ll and add a message:
<span># add everything for committing </span><span>git add . </span><span># commit to repo </span><span>git commit -m 'init project' </span>
Now I can directly navigate to the 404 page to check it out: http://localhost:8000/404.
Note that, when developing using gatsby develop, Gatsby will continue to use the default 404 page that overrides your custom 404 page.
Commit this and move on to the next part:
<span># this creates the folders in the root of the project </span><span>mkdir -p content/2021/03/{06/hello-world,07/second-post,08/third-post} </span><span># create individual files </span><span>touch content/2021/03/06/hello-world/index.mdx </span><span>touch content/2021/03/07/second-post/index.mdx </span><span>touch content/2021/03/08/third-post/index.mdx </span>
Dark mode is an essential feature of coding blogs. (I’m saying that half jokingly, in case you weren’t sure!) I’m going to use the Theme UI color mode hook useColorMode and do a simple toggle between the two modes I defined in the theme object earlier. Here’s what’s getting added to src/components/header.js:
<span><span>--- </span></span><span><span><span>title: Hello World - from mdx! </span></span></span><span><span><span>date: 2021-03-06</span> </span></span><span><span>---</span> </span> My first post!! <span><span>## h2 Heading</span> </span> Some meaningful prose <span><span>### h3 Heading</span> </span> Some other meaningful prose
But that doesn’t look great, so I’ll wrap the container with the Theme UI Flex component and shift the button over to the right:
<span><span>--- </span></span><span><span><span>title: Second Post! </span></span></span><span><span><span>date: 2021-03-07</span> </span></span><span><span>---</span> </span> This is my second post!
Git commit before moving to the next section:
<span><span>--- </span></span><span><span><span>title: Third Post! </span></span></span><span><span><span>date: 2021-03-08</span> </span></span><span><span>---</span> </span> This is my third post! <span>> with a block quote! </span> And a code block: <span><span>```js </span></span><span><span><span>const wheeeeee = true;</span> </span></span><span><span>```</span> </span>
The code blocks look a bit meh at the moment, so I’m going to add in some syntax highlighting with one of the many handy-dandy Theme UI packages. The one I’m using for this is Prism.
I’ll need to install the package and create a component in the gatsby-plugin-theme-ui folder called components.js:
<span>import <span>{ RainbowText }</span> from './components/rainbow'; </span>## <span>A Markdown Heading </span><span><span><span><RainbowText</span>></span>Wheeeeeeee<span><span></RainbowText</span>></span> </span>
In that file, I’ll need to define where I want to apply the Prism styles to, which is all pre and code tags:
<span># create the project directory </span><span>mkdir my-gatsby-blog </span><span># change into the directory </span><span>cd my-gatsby-blog </span><span># initialise a package.json file </span><span>yarn init -y </span><span># initialise the git repo </span><span>git init </span>
With that defined, I’ll also need to define in the theme object which Prism theme I want to use:
<span># create .gitignore file in my directory </span><span>touch .gitignore </span><span># add ignore contents with echo </span><span>echo "# Project dependencies </span><span>.cache </span><span>node_modules </span><span> </span><span># Build directory </span><span>public </span><span> </span><span># Other </span><span>.DS_Store </span><span>yarn-error.log" > .gitignore </span>
Another stop and start of the dev server is needed to see the changes take effect!
Commit the changes and move onto the next section:
<span>yarn add gatsby react react-dom </span><span># -p is to create parent directories too if needed </span><span>mkdir -p src/pages </span><span># create the index (home) page </span><span>touch src/pages/index.js </span>
This next bit is optional. Markdown JSX allows React (JSX) components to be included in the Markdown. To demonstrate this, I’m going to add a RainbowText component that will animate some colors on an animation cycle. There’s an additional dependency I need for the animation: keyframes from @emotion/react. I’ll install that now:
<span>import <span>React</span> from "react"; </span> <span>export default function <span>IndexPage</span>() { </span> <span>return <span><span><h1</span>></span>Hello, World!<span><span></h1</span>></span>; </span><span>} </span>
This will probably trash the dev server if it’s running, so I’ll stop it for now.
In the src/components/rainbow-text.js file, I’ll be adding this component:
<span># if you're using npm ? </span><span># $(npm bin)/gatsby develop </span><span>yarn gatsby develop </span>
As this is optional, I won’t be going into detail on what’s going on here. Just know that it’s a nice CSS effect to have on hover.
With that component created, I can import it into any .mdx file I want to use it in. In this example, I’m adding it to content/2021/03/third-post/index.mdx. Here’s the diff of the file now that I’ve added the component:
<span>"scripts": { </span> <span>"build": "gatsby build", </span> <span>"dev": "gatsby develop", </span> <span>"serve": "gatsby serve", </span> <span>"clean": "gatsby clean" </span><span>}, </span>
After starting up the dev server again, I can go to the post where that component has been added, and when I hover over the text being wrapped in
You’ll probably be grimacing at that import: ../../../. On and on! There’s a way to go around this, however, using the root wrapper concept I detailed earlier and using the MDXProvider which will — ahem! — provide MDX with any components you pass to it.
Going back to the root wrapper (root-wrapper.js), I can wrap the page element with the MDXProvider and pass the RainbowText component to the MDXProvider:
<span>"scripts": { </span> <span>"build": "gatsby build", </span> <span>"dev": "gatsby develop -p 8945 -o", </span> <span>"serve": "gatsby serve -p 9854 -o", </span> <span>"clean": "gatsby clean" </span><span>}, </span>
Now I can remove the import from the .mdx file:
<span># add everything for committing </span><span>git add . </span><span># commit to repo </span><span>git commit -m 'init project' </span>
After stopping and restarting the dev server, I can go to this post and still see the RainbowText working. The extra advantage of adding components directly to the MDXProvider is that there’s no need to import a component into the .mdx document when you want to use it. It’s available via the provider for all MDX documents.
I’ll commit this now:
<span># this creates the folders in the root of the project </span><span>mkdir -p content/2021/03/{06/hello-world,07/second-post,08/third-post} </span><span># create individual files </span><span>touch content/2021/03/06/hello-world/index.mdx </span><span>touch content/2021/03/07/second-post/index.mdx </span><span>touch content/2021/03/08/third-post/index.mdx </span>
If I want to add images to my blog posts, I can include them in the MDX files, something like this:
<span>import <span>{ RainbowText }</span> from './components/rainbow'; </span>## <span>A Markdown Heading </span><span><span><span><RainbowText</span>></span>Wheeeeeeee<span><span></RainbowText</span>></span> </span>
The ./mdx-logo.png is a file I’ve added to the content/2021/03/06/hello-world folder, and I’m referencing it as a relative file. That’s not it for this, though. If I go to the hello world post, the image being displayed is broken. I’m going to need to add gatsby-remark-images as a plugin to gatsby-plugin-mdx so it knows what to do with the image files:
<span># create the project directory </span><span>mkdir my-gatsby-blog </span><span># change into the directory </span><span>cd my-gatsby-blog </span><span># initialise a package.json file </span><span>yarn init -y </span><span># initialise the git repo </span><span>git init </span>
I’ll then need to configure the plugins in gatsby-config.js:
<span># create .gitignore file in my directory </span><span>touch .gitignore </span><span># add ignore contents with echo </span><span>echo "# Project dependencies </span><span>.cache </span><span>node_modules </span><span> </span><span># Build directory </span><span>public </span><span> </span><span># Other </span><span>.DS_Store </span><span>yarn-error.log" > .gitignore </span>
The additional gatsby-source-filesystem object is letting Gatsby know where to look for the images to be processed.
Commit this now:
<span>yarn add gatsby react react-dom </span><span># -p is to create parent directories too if needed </span><span>mkdir -p src/pages </span><span># create the index (home) page </span><span>touch src/pages/index.js </span>
SEO is quite important if I want to have my content found on the Internet by search engines, so I’ll need to add the relevant meta tags to my blog here. It can be quite an involved process defining all the relevant tags needed, so to save time, I’ve created a React SEO Component for use in Gatsby for generating all the meta tags needed.
I’m going to yarn add the component along with the dependencies needed for it to work:
<span>import <span>React</span> from "react"; </span> <span>export default function <span>IndexPage</span>() { </span> <span>return <span><span><h1</span>></span>Hello, World!<span><span></h1</span>></span>; </span><span>} </span>
I’ll need to add the gatsby-plugin-react-helmet to the gatsby-config.js plugin array:
<span># if you're using npm ? </span><span># $(npm bin)/gatsby develop </span><span>yarn gatsby develop </span>
Then it’s a case of using the SEO component throughout the site where I need to have meta tags.
The component takes quite a few props, many of which are defined once throughout the site, so the best place to add these would be in the siteMetadata object. Then I can pull out what I need with the useSiteMetadata hook.
I’m going to add several more properties to the siteMetadata object:
<span>"scripts": { </span> <span>"build": "gatsby build", </span> <span>"dev": "gatsby develop", </span> <span>"serve": "gatsby serve", </span> <span>"clean": "gatsby clean" </span><span>}, </span>
If you’re following along, you can change these as needed. The siteUrl can be a dummy URL for now. That’s to help with pointing to any images needed for use in Open Graph protocol, and it’s the image you see when sharing a post you have made on Twitter, Facebook, LinkedIn and Reddit, for example.
Now that those additional properties are on the siteMetadata object, I’ll need to be able to query them. Currently the useSiteMetadata hook only has title and description, so I’ll add the rest in now:
<span>"scripts": { </span> <span>"build": "gatsby build", </span> <span>"dev": "gatsby develop -p 8945 -o", </span> <span>"serve": "gatsby serve -p 9854 -o", </span> <span>"clean": "gatsby clean" </span><span>}, </span>
I’ll add the SEO component to all the pages. First up, I’ll do the posts pages in the src/pages/{mdx.slug}.js page. This is one of the most involved, so I’ll dump out the difference here and detail what’s going on:
<span># add everything for committing </span><span>git add . </span><span># commit to repo </span><span>git commit -m 'init project' </span>
The siteUrl, slug and excerpt are needed for the canonical link (very important in SEO) and the excerpt is for the meta description.
I’m using the siteMetadata hook to get the rest of the information the component needs. title and titleTemplate are used to make up what you see in the browser tab.
The article Boolean is for the component, so it can create the breadcrumb list in JSONLD format. The rest of the props are to help identify the author and published date. ?
That was a lot. I hope some of it made sense! For the scope of this post, I’ll leave it there, but there’s a lot more to dig into on this subject, and I mean a lot!
Thankfully the src/pages/index.js page is a bit simpler!
<span>import <span>{ RainbowText }</span> from './components/rainbow'; </span>## <span>A Markdown Heading </span><span><span><span><RainbowText</span>></span>Wheeeeeeee<span><span></RainbowText</span>></span> </span>
I’ve intentionally left out the image from both examples. If you’re interested in making your own Open Graph images to use in this component, check out the post “Open Graph Images with Gatsby and Vercel” for how to do this with a serverless function. ?
Now I can build the site (almost ready for production), and once it’s built I can check out the page source for the meta tags:
<span># create the project directory </span><span>mkdir my-gatsby-blog </span><span># change into the directory </span><span>cd my-gatsby-blog </span><span># initialise a package.json file </span><span>yarn init -y </span><span># initialise the git repo </span><span>git init </span>
Once the build has finished, I can use yarn serve to have the built site served locally on localhost:9000. In the browser, I can view the page source with the keyboard shortcut Ctrl u. From here, I can check for the canonical meta tag, which will be the dummy URL used in the metadata.
Alrighty! Commit this to Git and move on:
<span># create .gitignore file in my directory </span><span>touch .gitignore </span><span># add ignore contents with echo </span><span>echo "# Project dependencies </span><span>.cache </span><span>node_modules </span><span> </span><span># Build directory </span><span>public </span><span> </span><span># Other </span><span>.DS_Store </span><span>yarn-error.log" > .gitignore </span>
You may be wondering why I’ve been making Git commits at the end of each section. That’s because I’m going to push the project up to GitHub now.
I’ll log in to my GitHub account and select the plus icon next to my avatar image on the top right corner and select New repository.
In the Repository name, I’ll add in the project name my-gatsby-blog but leave the rest of the defaults and click Create repository.
The next screen gives me the terminal commands I need to push my local project to GitHub:
<span>yarn add gatsby react react-dom </span><span># -p is to create parent directories too if needed </span><span>mkdir -p src/pages </span><span># create the index (home) page </span><span>touch src/pages/index.js </span>
Once you’ve put all those into the terminal and hit Enter, refresh the GitHub page to see the new project!
Time to put this baby on the Web! There are many ways to do this. Because Gatsby builds to a flat file structure, you can host a Gatsby site on any file server with access to the Internet.
There are many services out there that offer hosting on a CDN, many for free! Services like Netlify, Vercel and Render will allow you to push your built site to their CDNs via a CLI, GitHub integration, or, in the case of Netlify, a straight up drag and drop!
To deploy with Vercel, you’ll need a GitHub, GitLab or Bitbucket account to authenticate with. Then you’ll be prompted to install the Vercel CLI:
<span>import <span>React</span> from "react"; </span> <span>export default function <span>IndexPage</span>() { </span> <span>return <span><span><h1</span>></span>Hello, World!<span><span></h1</span>></span>; </span><span>} </span>
I already have it installed, so now it’s a case of running the CLI command:
<span># if you're using npm ? </span><span># $(npm bin)/gatsby develop </span><span>yarn gatsby develop </span>
I’m then prompted to set up and deploy the new project. I’m going to answer the default to all the questions with Enter:
<span>"scripts": { </span> <span>"build": "gatsby build", </span> <span>"dev": "gatsby develop", </span> <span>"serve": "gatsby serve", </span> <span>"clean": "gatsby clean" </span><span>}, </span>
That’s it. I’m then given a deployment URL where I can watch the build of the site on Vercel.
From the Vercel dashboard I can configure the domain, and also buy one from Vercel if I want. I personally use Namecheap.com, but it’s an option.
Deploying with Netlify via the CLI is much the same as with Vercel, but I’m going to do the drag-and-drop creation.
For authentication, I’ll need one of GitHub, GitLab, Bitbucket or email account. Once I’ve authenticated and logged in, I can select Sites in the menu bar, then there’s a drop area Want to deploy a new site without connecting to Git? Drag and drop your site output folder here. I’m going to navigate in my file explorer to the root of my project and drag and drop the public folder to the drop area.
Netlify will build the files and deploy them to a generated URL for inspection. Much the same as with Vercel, Netlify will let you purchase a domain there and deploy to it.
Render doesn’t have a CLI or drop option and instead uses a GitHub integration. To authenticate, I’ll need a GitHub, GitLab or Google account. Once I’ve authenticated and logged in, I’m on the services section. From here, I can select New Static Site then enter my GitHub URL for the project I pushed to GitHub earlier.
On the next page, I’ll give it the following settings:
Then click Create Static Site.
Wait for Render to do its thing, and then click the link below the project name to see the site live.
Render also has the option to set your own custom domain for the site!
There are many more Gatsby plugins to choose from for adding additional functionality. I’ll leave these to you if you want to add more. For example:
If you’re interested in knowing how popular your site is, there are analytics options. I stopped using Google Analytics a while back on my own projects, and I now prefer more privacy-focused alternatives. One I recommend is Fathom Analytics. (I have an affiliate link if you want to get $10 off your first month’s subscription.)
Another alternative is Plausible, which I’ve also heard good things about.
To implement Fathom Analytics on a Gatsby site, I’ll need to add an additional script tag to the head of my site. What does that mean? Well, first up I’ll need to create the site on my Fathom dashboard, then go to https://app.usefathom.com/#/settings/sites, scroll to the bottom of the list, add in my new site (my-gatsby-blog), then click Get site code. I then get a popup modal with the site code. I’ll need that for the script I’m going to add to the head of my Gatsby project. Here’s what the script looks like:
<span>import <span>{ RainbowText }</span> from './components/rainbow'; </span>## <span>A Markdown Heading </span><span><span><span><RainbowText</span>></span>Wheeeeeeee<span><span></RainbowText</span>></span> </span>
Here’s the diff of root-wrapper.js:
<span># create the project directory </span><span>mkdir my-gatsby-blog </span><span># change into the directory </span><span>cd my-gatsby-blog </span><span># initialise a package.json file </span><span>yarn init -y </span><span># initialise the git repo </span><span>git init </span>
That’s it from me. Thank you so much for making it to the end. ?
I hope you got what you needed from this quite extensive guide on setting up a Gatsby project from scratch!
If you want to reach out and say hi, the best place to get me is on Twitter.
Adding images to your Gatsby MDX blog posts can enhance the visual appeal and make your content more engaging. To do this, you need to import the image file into your MDX file. First, place your image file in the ‘src’ directory of your Gatsby project. Then, in your MDX file, import the image using the syntax: import ImageName from '../path/to/image.jpg'. After importing, you can use the image in your MDX content by using the ‘ImageName’ as a component:
Gatsby MDX allows you to customize the layout of your blog posts. You can create a layout component in your ‘src’ directory. This component can include elements like a header, footer, or a sidebar. Once you’ve created your layout component, you can wrap your MDX content with it. To do this, import the layout component in your MDX file and then wrap your content with it like this:
Gatsby MDX supports the inclusion of code snippets in your blog posts. To add a code snippet, you can use the ‘pre’ and ‘code’ tags. Wrap your code snippet with these tags like this:
<code> Your code here </code>. You can also specify the language of the code snippet for syntax highlighting by adding it after the first set of backticks like this: “`javascript.
A table of contents can make your blog posts more navigable. Gatsby MDX supports the creation of a table of contents. You can use the ‘tableOfContents’ field in your GraphQL query to generate a table of contents. This field returns an array of headings and their respective depths which you can use to create a nested list for your table of contents.
SEO is crucial for increasing the visibility of your blog. Gatsby MDX allows you to add SEO to your blog posts. You can use the ‘gatsby-plugin-react-helmet’ to manage the document head of your blog posts. This plugin allows you to add elements like title, description, and meta tags which are important for SEO.
Pagination can improve the user experience of your blog by making it easier to navigate through your posts. Gatsby MDX supports pagination. You can use the ‘gatsby-awesome-pagination’ plugin to create paginated pages. This plugin creates a paginated index and also paginated context for your pages.
Adding a comment section can increase engagement on your blog. Gatsby MDX allows you to add comments to your blog posts. You can use third-party services like Disqus or Commento. These services provide a script that you can include in your layout component to enable comments.
Social sharing buttons can increase the reach of your blog posts. Gatsby MDX allows you to add social sharing buttons. You can use plugins like ‘gatsby-plugin-react-share’ to add social sharing buttons. This plugin supports various social media platforms like Facebook, Twitter, and LinkedIn.
A search functionality can improve the user experience of your blog by making it easier to find specific posts. Gatsby MDX supports the addition of a search functionality. You can use plugins like ‘gatsby-plugin-elasticlunr-search’ to add a search functionality. This plugin creates an index of your posts which can be searched using keywords.
A newsletter subscription form can help you build an email list and keep your readers updated. Gatsby MDX allows you to add a newsletter subscription form. You can use services like Mailchimp or Sendinblue. These services provide a form that you can embed in your layout component to enable newsletter subscriptions.
The above is the detailed content of How to Build a Developer Blog with Gatsby and MDX. For more information, please follow other related articles on the PHP Chinese website!