Build a Web App with Modern JavaScript and Web Components

Build a Web App with Modern JavaScript and Web Components – SitePointSkip to main contentFree JavaScript Book!Write powerful, clean and maintainable JavaScript.RRP $11.95 JavaScript in the browser has evolved. Developers who want to take advantage of the latest features have the option of going framework-less with less hassle. Options normally reserved to front-end frameworks, such as a component-based approach, is now feasible in plain old JavaScript.
In this take, I’ll showcase all the latest JavaScript features, using a UI that features author data with a grid and a search filter. To keep it simple, once a technique gets introduced, I’ll move on to the next technique so as not to belabor the point. For this reason, the UI will have an Add option, and a dropdown search filter. The author model will have three fields: name, email, and an optional topic. Form validation will be included mostly to show this framework-less technique without being thorough.
The once plucky language has grown up with many modern features such as Proxies, import/export, the optional chain operator, and web components. This fits perfectly within the Jamstack, because the app renders on the client via HTML and vanilla JavaScript.
I’ll leave out the API to stay focused on the app, but I’ll point to where this integration can occur within the app.
Getting Started
The app is a typical JavaScript app with two dependencies: an http-server and Bootstrap. The code will only run in the browser, so there’s no back end other than one to host static assets. The code is up on GitHub for you to play with.
Assuming you have the latest Node LTS installed on the machine:
mkdir framework-less-web-components
cd framework-less-web-components
npm init

This should end up with a single package.json file where to put dependencies.
To install the two dependencies:
npm i http-server bootstrap@next –save-exact

http-server: an HTTP server to host static assets in the Jamstack
Bootstrap: a sleek, powerful set of CSS styles to ease web development
If you feel http-server isn’t a dependency, but a requirement for this app to run, there’s the option to install it globally via npm i -g http-server. Either way, this dependency isn’t shipped to the client, but only serves static assets to the client.
Open the package.json file and set the entry point via “start”: “http-server” under scripts. Go ahead and fire up the app via npm start, which will make http://localhost:8080/ available to the browser. Any index.html file put in the root folder gets automatically hosted by the HTTP server. All you do is a refresh on the page to get the latest bits.
The folder structure looks like this:

┣━┓ components
┃ ┣━━ App.js
┃ ┣━━ AuthorForm.js
┃ ┣━━ AuthorGrid.js
┃ ┗━━ ObservableElement.js
┣━┓ model
┃ ┣━━ actions.js
┃ ┗━━ observable.js
┣━━ index.html
┣━━ index.js
┗━━ package.json

This is what each folder is meant for:
components: HTML web components with an App.js and custom elements that inherit from ObservableElement.js
model: app state and mutations that listen for UI state changes
index.html: main static asset file that can be hosted anywhere
To create the folders and files in each folder, run the following:
mkdir components model
touch components/App.js components/AuthorForm.js components/AuthorGrid.js components/ObservableElement.js model/actions.js model/observable.js index.html index.js

Integrate Web Components
In a nutshell, web components are custom HTML elements. They define the custom element that can be put in the markup, and declare a callback method that renders the component.
Here’s a quick rundown of a custom web component:
class HelloWorldComponent extends HTMLElement {
connectedCallback() {
this.innerHTML = ‘Hello, World!’
}
}

window.customElements.define(‘hello-world’, HelloWorldComponent)

If you feel you need a more gentle introduction to web components, check out the MDN article. At first, they may feel magical, but a good grasp of the callback method makes this perfectly clear.
The main index.html static page declares the HTML web components. I’ll use Bootstrap to style HTML elements and bring in the index.js asset that becomes the app’s main entry point and gateway into JavaScript.
Bust open the index.html file and put this in place:

Framework-less Components

Authors

Hit Enter to add an author entry

Created with ❤ By C R

Topic
JavaScript
HTMLElement
ES7+

Search by
All
JavaScript
HTMLElement
ES7+

Name
Email
Topic

Framework-less Components with Observables

Pay close attention to the script tag with a type attribute set to module. This is what unlocks import/export in vanilla JavaScript in the browser. The template tag with an id defines the HTML elements that enable web components. I’ve broken up the app into three main components: html-app, author-form, and author-grid. Because nothing’s defined in JavaScript yet, the app will render the navigation bar without any of the custom HTML tags.
To start off easy, place this in ObservableElement.js. It’s the parent element to all the author components:
export default class ObservableElement extends HTMLElement {
}

Then, define the html-app component in App.js:
export default class App extends HTMLElement {
connectedCallback() {
this.template = document
.getElementById(‘html-app’)

window.requestAnimationFrame(() = > {
const content = this.template
.content
.firstElementChild
.cloneNode(true)

this.appendChild(content)
})
}
}

Note the use of export default to declare JavaScript classes. This is the capability I enabled via the module type when I referenced the main script file. To use web components, inherit from HTMLElement and define the connectedCallback class method. The browser takes care of the rest. I’m using requestAnimationFrame to render the main template before the next repaint in the browser.
This is a common technique you’ll see with web components. First, grab the template via an element ID. Then, clone the template via cloneNode. Lastly, appendChild the new content into the DOM. If you run into any problems where web components don’t render, be sure to check that the cloned content got appended to the DOM first.
Next, define the AuthorGrid.js web component. This one will follow a similar pattern and manipulate the DOM a bit:
import ObservableElement from ‘./ObservableElement.js’

export default class AuthorGrid extends ObservableElement {
connectedCallback() {
this.template = document
.getElementById(‘author-grid’)
this.rowTemplate = document
.getElementById(‘author-row’)
const content = this.template
.content
.firstElementChild
.cloneNode(true)
this.appendChild(content)

this.table = this.querySelector(‘table’)
this.updateContent()
}

updateContent() {
this.table.style.display =
(this.authors?.length ?? 0) === 0
? ‘none’
: ”

this.table
.querySelectorAll(‘tbody tr’)
.forEach(r = > r.remove())
}
}

I defined the main this.table element with a querySelector. Because this is a class, it’s possible to keep a nice reference to the target element by using this. The updateContent method mostly nukes the main table when there are no authors to show in the grid. The optional chaining operator (?.) and null coalescing takes care of setting the display style to none.
Take a look at the import statement, because it brings in the dependency with a fully qualified extension in the file name. If you’re used to Node development, this is where it differs from the browser implementation, which follows the standard, where this does require a file extension like .js. Learn from me and be sure to put the file extension while working in the browser.
Next, the AuthorForm.js component has two main parts: render the HTML and wire up element events to the form.
To render the form, open AuthorForm.js:
import ObservableElement from ‘./ObservableElement.js’

export default class AuthorForm extends ObservableElement {
connectedCallback() {
this.template = document
.getElementById(‘author-form’)
const content = this.template
.content
.firstElementChild
.cloneNode(true)

this.appendChild(content)

this.form = this.querySelector(‘form’)
this.form.querySelector(‘input’).focus()
}

resetForm(inputs) {
inputs.forEach(i = > {
i.value = ”
i.classList.remove(‘is-valid’)
})
inputs[0].focus()
}
}

The focus guides the user to start typing on the first input element available in the form. Be sure to place any DOM selectors after the appendChild, as otherwise this technique won’t work. The resetForm isn’t used right now but will reset the state of the form when the user presses Enter.
Wire up events via addEventListener by appending this code inside the connectedCallback method. This can be added to the very end of the connectedCallback method:
this.form
.addEventListener(‘keypress’, e = > {
if (e.key === ‘Enter’) {
const inputs = this.form.querySelectorAll(‘input’)
const select = this.form.querySelector(‘select’)

console.log(‘Pressed Enter: ‘ +
inputs[0].value + ‘|’ +
inputs[1].value + ‘|’ +
(select.value === ‘Topic’ ? ” : select.value))

this.resetForm(inputs)
}
})

this.form
.addEventListener(‘change’, e = > {
if (e.target.matches(‘select.search’)
&& e.target.value !== ‘Search by’) {
console.log(‘Filter by: ‘ + e.target.value)
}
})

These are typical event listeners that get attached to the this.form element in the DOM. The change event uses event delegation to listen for all change events in the form but targets only the select.search element. This is an effective way to delegate a single event to as many target elements in the parent element. With this in place, typing anything in the form and hitting Enter resets the form back to zero state.
To get these web components to render on the client, open index.js and put this in:
import AuthorForm from ‘./components/AuthorForm.js’
import AuthorGrid from ‘./components/AuthorGrid.js’
import App from ‘./components/App.js’

window.customElements.define(‘author-form’, AuthorForm)
window.customElements.define(‘author-grid’, AuthorGrid)
window.customElements.define(‘html-app’, App)

Feel free to refresh the page in the browser now and play with the UI. Open up your developer tools and look at the console messages as you click and type in the form. Pressing the Tab key should help you navigate between input elements in the HTML document.
Validate the Form
From playing around with the form you may notice it takes in arbitrary input when both the name and email are required, and the topic is optional. The framework-less approach can be a combination of HTML validation and a bit of JavaScript. Luckily, Bootstrap makes this somewhat easy by adding/removing CSS class names via the classList web API.
Inside the AuthorForm.js component, find the console.log in the Enter key event handler, look for the log with “Pressed Enter”, and put this in right above it:
if (!this.isValid(inputs)) return

Then, define the isValid class method in AuthorForm. This may go above the resetForm method:
isValid(inputs) {
let isInvalid = false

inputs.forEach(i = > {
if (i.value && i.checkValidity()) {
i.classList.remove(‘is-invalid’)
i.classList.add(‘is-valid’)
} else {
i.classList.remove(‘is-valid’)
i.classList.add(‘is-invalid’)
isInvalid = true
}
})

return !isInvalid
}

In vanilla JavaScript, calling checkValidity uses the built-in HTML validator, because I tagged an input element with type=”email”. To check for required fields, a basic truthy check does the trick via i.value. The classList web API adds or removes CSS class names, so the Bootstrap styling can do its job.
Now, go ahead and give the app another try. Attempting to enter invalid data now gets flagged, and valid data now resets the form.
Observables
Time for the meat (or potatoes for my veggie friends) of this approach, because web components, and event handlers, can only take me so far. To make this app state-driven, I’ll need a way to track changes to the UI state. It turns out that observables are perfect for this, because they can fire updates to the UI when the state mutates. Think of observables as a sub/pub model, where subscribers listen for changes, and the publisher fires which changes took place in the UI state. This streamlines the amount of push and pull code necessary to build complex and exciting UIs without any framework.
Open the obserable.js file under model and put this in:
const cloneDeep = x = > JSON.parse(JSON.stringify(x))
const freeze = state = > Object.freeze(cloneDeep(state))

export default initialState = > {
let listeners = []

const proxy = new Proxy(cloneDeep(initialState), {
set: (target, name, value) = > {
target[name] = value
listeners.forEach(l = > l(freeze(proxy)))
return true
}
})

proxy.addChangeListener = cb = > {
listeners.push(cb)
cb(freeze(proxy))
return () = >
listeners = listeners.filter(el = > el !== cb)
}

return proxy
}

This may look scary at first, but it’s doing two things: hijacking the setter to catch mutations, and adding listeners. In ES6+, the Proxy class enables a proxy that wraps around the initialState object. This can intercept basic operations like this set method, which executes when there are changes to the object. Returning true in the setter lets the internal machinery in JavaScript know the mutation succeeded. The Proxy sets up a handler object where traps such as set get defined. Because I only care for mutations to the state object, the set has a trap. All other pieces of functionality, such as reads, get forwarded directly to the original state object.
Listeners keep a list of subscribed callbacks that want to be notified of mutations. The callback gets executed once after the listener get added, and it returns the listening callback for future reference.
The freeze and cloneDeep functions are put in place to prevent any further mutations of the underlying state object. This keeps the UI state more predictable and somewhat stateless because the data only moves in one direction.
Now, go to the actions.js file and put this in place:
export default state = > {
const addAuthor = author = > {
if (!author) return

state.authors = […state.authors, {
…author
}]
}

const changeFilter = currentFilter = > {
state.currentFilter = currentFilter
}

return {
addAuthor,
changeFilter
}
}

This is a testable JavaScript object that performs actual mutations to the state. For the sake of brevity, I’ll forgo writing unit tests but will leave this as an exercise for readers.
To fire mutations from the web components, they’ll need to be registered on the global window.applicationContext object. This makes this state object with mutations available to the rest of the app.
Open the main index.js file and add this right above where I registered the custom elements:
import observableFactory from ‘./model/observable.js’
import actionsFactory from ‘./model/actions.js’

const INITIAL_STATE = {
authors: [],
currentFilter: ‘All’
}

const observableState = observableFactory(INITIAL_STATE)
const actions = actionsFactory(observableState)

window.applicationContext = Object.freeze({
observableState,
actions
})

There are two objects available: the proxy observableState and the actions with mutations. The INITIAL_STATE bootstraps the app with initial data. This is what sets the initial zero config state. The action mutations take in the observable state and fire updates for all listeners by making changes to the observableState object.
Because mutations are not hooked up to the web components via applicationContext yet, the UI won’t track any changes. The web components will need HTML attributes to mutate and display state data. This is what comes next.
Observed Attributes
For web components, mutations to the state can be tracked via the attributes web API. These are getAttribute, setAttribute, and hasAttribute. With this arsenal, it’s more effective to persist UI state in the DOM.
Crack open ObservableElement.js and gut it out, replacing it with this code:
export default class ObservableElement extends HTMLElement {
get authors() {
if (!this.hasAttribute(‘authors’)) return []

return JSON.parse(this.getAttribute(‘authors’))
}

set authors(value) {
if (this.constructor
.observedAttributes
.includes(‘authors’)) {
this.setAttribute(‘authors’, JSON.stringify(value))
}
}

get currentFilter() {
if (!this.hasAttribute(‘current-filter’)) return ‘All’

return this.getAttribute(‘current-filter’)
}

set currentFilter(value) {
if (this.constructor
.observedAttributes
.includes(‘current-filter’)) {
this.setAttribute(‘current-filter’, value)
}
}

connectAttributes () {
window
.applicationContext
.observableState
.addChangeListener(state = > {
this.authors = state.authors
this.currentFilter = state.currentFilter
})
}

attributeChangedCallback () {
this.updateContent()
}
}

I purposely used snake casing in the current-filter attribute. This is because the attribute web API only supports lower case names. The getter/setter does the mapping between this web API and what the class expects, which is camel case.
The connectAttributes method in the web component adds its own listener to track state mutations. There’s an attributeChangedCallback available that fires when the attribute changes, and the web component updates the attribute in the DOM. This callback also calls updateContent to tell the web component to update the UI. The ES6+ getter/setter declares the same properties found in the state object. This it what makes this.authors, for example, accessible to the web component.
Note the use of constructor.observedAttributes. This is a custom static field I can declare now, so the parent class ObservableElement can track which attributes the web component cares about. With this, I can pick and choose which part of the state model is relevant to the web component.
I’ll take this opportunity to flesh out the rest of the implementation to track and change state via observables in each web component. This is what makes the UI “come alive” when there are state changes.
Go back to AuthorForm.js and make these changes. Code comments will tell you where to put it (or you can consult the repo):

static get observedAttributes() {
return [
‘current-filter’
]
}

this.addAuthor({
name: inputs[0].value,
email: inputs[1].value,
topic: select.value === ‘Topic’ ? ” : select.value
})

this.changeFilter(e.target.value)

super.connectAttributes()

addAuthor(author) {
window
.applicationContext
.actions
.addAuthor(author)
}

changeFilter(filter) {
window
.applicationContext
.actions
.changeFilter(filter)
}

updateContent() {

if (this.currentFilter !== ‘All’) {
this.form.querySelector(‘select’).value = this.currentFilter
}
this.resetForm(this.form.querySelectorAll(‘input’))
}

In the Jamstack, you may need to call a back-end API to persist the data. I recommend using the helper methods for these types of calls. Once the persisted state comes back from an API, it can be mutated within the app.
Lastly, find the AuthorGrid.js and wire up the observable attributes (the final file is here):

static get observedAttributes() {
return [
‘authors’,
‘current-filter’
]
}

super.connectAttributes()

getAuthorRow(author) {
const {
name,
email,
topic
} = author

const element = this.rowTemplate
.content
.firstElementChild
.cloneNode(true)
const columns = element.querySelectorAll(‘td’)

columns[0].textContent = name
columns[1].textContent = email
columns[2].textContent = topic

if (this.currentFilter !== ‘All’
&& topic !== this.currentFilter) {
element.style.display = ‘none’
}

return element
}

this.authors
.map(a = > this.getAuthorRow(a))
.forEach(e = > this.table
.querySelector(‘tbody’)
.appendChild(e))

Each web component can track different attributes, depending on what gets rendered in the UI. This is a nice clean way to separate components because it only deals with its own state data.
Go ahead and take this for a spin in the browser. Crack open the developer tools and inspect the HTML. You’ll see attributes set in the DOM, like current-filter, at the root of the web component. As you click and press Enter, note the app automatically tracks mutations to the state in the DOM.
Gotchas
For the pièce de résistance, be sure to leave the developer tools open, go to the JavaScript Debugger and find AuthorGrid.js. Then, set a breakpoint anywhere in updateContent. Select a search filter. Notice the browser hits this code more than once? This means code that updates the UI runs not once, but every time the state mutates.
This is because of this code that’s in ObservableElement:
window
.applicationContext
.observableState
.addChangeListener(state = > {
this.authors = state.authors
this.currentFilter = state.currentFilter
})

Currently, there are exactly two listeners that fire when there are changes to the state. If the web component tracks more than one state property, like this.authors, this fires that many more updates to the UI. This causes the UI to update inefficiently and may cause a lag with enough listeners and changes to the DOM.
To remedy this, open up ObservableElement.js and home in on the HTML attribute setters:

const equalDeep = (x, y) = > JSON.stringify(x) === JSON.stringify(y)

if (this.constructor.observedAttributes.includes(‘authors’)
&& !equalDeep(this.authors, value)) {

if (this.constructor.observedAttributes.includes(‘current-filter’)
&& this.currentFilter !== value) {

This adds a layer of defensive programming to detect attribute changes. When the web component realizes it doesn’t need to update the UI, it skips setting the attribute.
Now go back to the browser with the breakpoint, updating state should hit updateContent only once.

Final demo
This is what the app will look like with observables and web components:

And don’t forget, you can find the complete code on GitHub.
Conclusion
Framework-less apps via web components and observables have a nice way of building feature-rich UIs without any dependencies. This keeps the app payload lightweight and snappy for customers.

Best Affiliate WooCommerce Plugins Compared

WooCommerce and WordPress are, together, a powerful and winning combination for online commerce. Together they run 37% of eCommerce business online. Leveraging these two is the perfect combination for selling physical products, digital goods, and services online.  But having the most awesome WooCommerce storefront and the best products is just a tiny part of what…

Best WordPress and WooCommerce Review Plugins

Do you want to add customer reviews on your website? Reviews are the best free advertising for your business and your products.  they bring traffic and make customers spend more time engaging with your website content. they give credibility to your products and inspire trust among visitors. they are social proof that your products are reliable. …

Clean and animated website advice

I have a client that wants to offer service subscriptions (3 different plans), so no shipping involved though we would need to know the address of the users because we’ll be selling home services.We’re using shopify right now because I have limited experience and shopify has the bold subscription app that allows for recurring purchases and shopify as a whole allows for customer accounts. I tried square space initially and that didn’t work for subscriptions.The problem now is that the themes for shopify aren’t animated. There are very limited on scroll effects, or hover effects. Obviously these effects are done by web developers with years of experience. But I’m wondering if there is another platform i can use that has better themes/templates. Even the paid shopify themes arent what we’re looking for.Any suggestions? Wordpress has more options but some other posts have been made saying WP aren’t the best. Are there templates that look simple but have animations? What’s an easy way to get started making custom sites? I have an interest in graphic design and with this client i have been gaining experience with html, css, and js for about 6 months a few hours a week. Not looking to make this a career but more like a hobby (just as a background on my experience and commitment).This is a site that has the elements we would want: https://iti.ca/en/Thanks in advance

17 Best WordPress Star Rating Plugins

Star ratings are used by websites to review all kinds of products and services. You can use them to rate pretty much anything your users are interested in: a movie, the food in a restaurant, the quality of service provided by a mechanic, etc. One of the reasons star ratings are so popular is that…

How to Gain the Trust of Your Web Design Clients

Trust is the foundation of a good client/designer relationship. It provides confidence and security to everyone involved. Without it, there is no means to achieve any sort of comfort level or positive productivity. Just about every freelancer has a story about an untrustworthy client. Someone who promised the moon and delivered significantly less. Perhaps they…

Inventory and Stock Management Plugins for WooCommerce and WordPress

Do you want to instantly know the amount of inventory you have and where it is stored? Maybe you’ve been in the unenviable position of products being out-of-stock, with back orders piling up. When your inventory management is done well then order fulfillment goes smoothly. But you can’t achieve this if you’re using antiquated methods like…

How to Build a Developer Blog with Gatsby and MDX

How to Build a Developer Blog with Gatsby and MDX – SitePointSkip to main contentFree JavaScript Book!Write powerful, clean and maintainable JavaScript.RRP $11.95 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.
Why Gatsby?
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.
Markdown and MDX
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:
import { RainbowText } from ‘./components/rainbow’;
## A Markdown Heading
Wheeeeeeee

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.
What Do I need?
If you’re going to follow along, there’s a few things you’ll need:
a basic web development setup: Node, terminal (bash, zsh or fish)
a text editor
a basic understanding of React
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!
Hello, World!
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:

mkdir my-gatsby-blog

cd my-gatsby-blog

yarn init -y

git init

Cool. Now, before going anywhere else with this, I’m going to need to add a .gitignore file before installing any npm modules:

touch .gitignore

echo “# Project dependencies
.cache
node_modules

# Build directory
public

# Other
.DS_Store
yarn-error.log” > .gitignore

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:
yarn add gatsby react react-dom

mkdir -p src/pages

touch src/pages/index.js

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:
import React from “react”;

export default function IndexPage() {
return Hello, World!;
}

I’m now ready to run the Gatsby develop command from the command line:

yarn gatsby develop

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:
“scripts”: {
“build”: “gatsby build”,
“dev”: “gatsby develop”,
“serve”: “gatsby serve”,
“clean”: “gatsby clean”
},

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:
“scripts”: {
“build”: “gatsby build”,
“dev”: “gatsby develop -p 8945 -o”,
“serve”: “gatsby serve -p 9854 -o”,
“clean”: “gatsby clean”
},

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:

git add .

git commit -m ‘init project’

Content for the Blog
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:

mkdir -p content/2021/03/{06/hello-world,07/second-post,08/third-post}

touch content/2021/03/06/hello-world/index.mdx
touch content/2021/03/07/second-post/index.mdx
touch content/2021/03/08/third-post/index.mdx

I’ll be using these throughout the examples I’m making.
You’ll notice the file extension .mdx. This is an MDX file.
Front matter
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:

title: Hello World – from mdx!
date: 2021-03-06

My first post!!

## h2 Heading

Some meaningful prose

### h3 Heading

Some other meaningful prose

Here’s our second post:

title: Second Post!
date: 2021-03-07

This is my second post!

A third post:

title: Third Post!
date: 2021-03-08

This is my third post!

> with a block quote!

And a code block:

“`js
const wheeeeee = true;
“`

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:

git add .

git commit -m ‘add markdown files’

Gatsby Config
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:
touch gatsby-config.js

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.
Gatsby Plugins
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:
yarn add gatsby-plugin-mdx @mdx-js/mdx @mdx-js/react gatsby-source-filesystem

A quick look at the package.json now shows that I have the following dependency version installed:
“dependencies”: {
“@mdx-js/mdx”: “^1.6.22”,
“@mdx-js/react”: “^1.6.22”,
“gatsby”: “^3.1.1”,
“gatsby-plugin-mdx”: “^2.1.0”,
“gatsby-source-filesystem”: “^3.1.0”,
“react”: “^17.0.1”,
“react-dom”: “^17.0.1”
},

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:
module.exports = {
plugins: [
`gatsby-plugin-mdx`,
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/content`,
name: `content`,
},
},
],
};

Commit changes up to now:
git add .
git commit -m ‘add gatsby plugins’

Gatsby GraphQL
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:
You can now view my-gatsby-blog in the browser.

http://localhost:8000/

View GraphiQL, an in-browser IDE, to explore your site’s data and schema

http://localhost:8000/___graphql

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.
Query Local Files with GraphQL
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:
{
allMdx {
nodes {
frontmatter {
title
date
}
}
}
}

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:
{
“data”: {
“allMdx”: {
“nodes”: [
{
“frontmatter”: {
“title”: “Hello World – from mdx!”,
“date”: “2021-03-06T00:00:00.000Z”
}
},
{
“frontmatter”: {
“title”: “Second Post!”,
“date”: “2021-03-07T00:00:00.000Z”
}
},
{
“frontmatter”: {
“title”: “Third Post!”,
“date”: “2021-03-08T00:00:00.000Z”
}
}
]
}
},
“extensions”: {}
}

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:
module.exports = {
siteMetadata: {
title: `My Gatsby Blog`,
description: `This is my coding blog.`,
},
plugins: [

{

},
],
};

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:
const siteMetadata = {
title: `My Gatsby Blog`,
description: `This is my coding blog.`,
};

Then add the siteMetadata object to the Gatsby config file:
const siteMetadata = {
title: `My Gatsby Blog`,
description: `This is my coding blog.`,
};

module.exports = {
siteMetadata,
plugins: [

{

},
],
};

Now I can hop over to the GraphiQL explorer again and query that site metadata with the following query:
{
site {
siteMetadata {
title
description
}
}
}

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:
{
“data”: {
“site”: {
“siteMetadata”: {
“title”: “My Gatsby Blog”,
“description”: “This is my coding blog.”
}
}
},
“extensions”: {}
}

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:
import { graphql, useStaticQuery } from “gatsby”;
import React from “react”;

export default function IndexPage() {
const {
site: { siteMetadata },
} = useStaticQuery(graphql`
{
site {
siteMetadata {
title
description
}
}
}
`);
console.log(“=====================”);
console.log(siteMetadata);
console.log(“=====================”);
return Hello World!;
}

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:
=====================
{title: “My Gatsby Blog”, description: “This is my coding blog.”}
description: “This is my coding blog.”
title: “My Gatsby Blog”
__proto__: Object
=====================

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:

mkdir src/hooks

touch src/hooks/use-site-metadata.js

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:
import { graphql, useStaticQuery } from “gatsby”;
export const useSiteMetadata = () = > {
const { site } = useStaticQuery(
graphql`
query SITE_METADATA_QUERY {
site {
siteMetadata {
title
description
}
}
}
`
);
return site.siteMetadata;
};

You may have noticed that this query isn’t the same as the one from from the GraphiQL explorer:
+ query SITE_METADATA_QUERY {
site {
siteMetadata {
title
description
}
}
}

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:
import React from “react”;
import { useSiteMetadata } from “../hooks/use-site-metadata”;

export default function IndexPage() {
const { title, description } = useSiteMetadata();
return (

{title}
{description}

);
}

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:
git add .
git commit -m ‘add site metadata and metadata hook’

Styling with Theme UI
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:
yarn add theme-ui gatsby-plugin-theme-ui @theme-ui/presets

With those installed, I’ll need to add the gatsby-plugin-theme-ui to the gatsby-config.js plugin array:
module.exports = {
siteMetadata,
plugins: [
`gatsby-plugin-theme-ui`,
`gatsby-plugin-mdx`,
{
resolve: `gatsby-source-filesystem`,

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:

mkdir src/gatsby-plugin-theme-ui

touch src/gatsby-plugin-theme-ui/index.js

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:
import { deep, swiss } from “@theme-ui/presets”;

const theme = {
…swiss,
colors: {
…swiss.colors,
modes: {
dark: {
…deep.colors,
},
},
},

styles: {
…swiss.styles,
p: {
fontFamily: “body”,
fontWeight: “body”,
lineHeight: “body”,
fontSize: 3,
},
},
};

export default theme;

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:
git add .
git commit -m ‘add Theme UI and configure presets’

Time to add some React components!
Layout Component
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:

mkdir src/components

touch src/components/header.js src/components/layout.js

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:
import { Link as GatsbyLink } from “gatsby”;
import React from “react”;
import { Box, Heading, Link } from “theme-ui”;

export const Header = ({ siteTitle, siteDescription }) = > {
return (

{siteTitle}

{siteDescription}

);
};

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:
import React from “react”;
import { Box } from “theme-ui”;
import { useSiteMetadata } from “../hooks/use-site-metadata”;
import { Header } from “./header”;

export const Layout = ({ children }) = > {
const { title, description } = useSiteMetadata();
return (

{children}

);
};

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:

This is wrapped

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:
import React from “react”;
import { Layout } from “../components/layout”;

export default function IndexPage() {
return (

This is wrapped

);
}

The result is the header, provided in the Layout component and the H1 This is wrapped.
Index Page Posts Query
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:
{
allMdx(sort: { fields: [frontmatter___date], order: DESC }) {
nodes {
id
slug
excerpt(pruneLength: 250)
frontmatter {
title
date(formatString: “YYYY MMMM Do”)
}
}
}
}

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:
{
“data”: {
“allMdx”: {
“nodes”: [
{
“id”: “2bed526a-e5a9-5a00-b9c0-0e33beafdbcf”,
“slug”: “2021/03/08/third-post/”,
“excerpt”: “This is my third post! with a block quote! And a code block:”,
“frontmatter”: {
“title”: “Third Post!”,
“date”: “2021 March 8th”
}
},
{
“id”: “89ea266b-c981-5d6e-87ef-aa529e98946e”,
“slug”: “2021/03/07/second-post/”,
“excerpt”: “This is my second post!”,
“frontmatter”: {
“title”: “Second Post!”,
“date”: “2021 March 7th”
}
},
{
“id”: “75391ba1-3d6b-539f-86d2-d0e6b4104806”,
“slug”: “2021/03/06/hello-world/”,
“excerpt”: “My first post!! h2 Heading Some meaningful prose h3 Heading Some other meaningful prose”,
“frontmatter”: {
“title”: “Hello World – from mdx!”,
“date”: “2021 March 6th”
}
}
]
}
},
“extensions”: {}
}

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:
import { graphql, Link as GatsbyLink } from “gatsby”;
import React from “react”;
import { Box, Heading, Link } from “theme-ui”;
import { Layout } from “../components/layout”;

export default function IndexPage({ data }) {
return (

{data.allMdx.nodes.map(({ id, excerpt, frontmatter, slug }) = > (

{frontmatter.title}

{frontmatter.date}

{excerpt}

))}

);
}

export const query = graphql`
query SITE_INDEX_QUERY {
allMdx(sort: { fields: [frontmatter___date], order: DESC }) {
nodes {
id
excerpt(pruneLength: 250)
frontmatter {
title
date(formatString: “YYYY MMMM Do”)
}
slug
}
}
}
`;

This uses the components previously detailed. Note that the excerpt, frontmatter, and slug are being destructured from data.allMdx.nodes:
{data.allMdx.nodes.map(({ excerpt, frontmatter, slug }) = > (

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:
git add .
git commit -m ‘add Header and Layout components’

Using the Gatsby File System Route API with MDX
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:

touch src/pages/{mdx.slug}.js

In the file, I’ll define a GraphQL query for the data I want to include in this template:
import { graphql } from “gatsby”;
import { MDXRenderer } from “gatsby-plugin-mdx”;
import React from “react”;
import { Box } from “theme-ui”;

export default function PostPage({ data }) {
const {
body,
frontmatter: { title },
} = data.mdx;
return (

{title}

{body}

);
}

export const query = graphql`
query POST_BY_SLUG($slug: String) {
mdx(slug: { eq: $slug }) {
id
slug
body
frontmatter {
date
title
}
}
}
`;

Now that’s a lot of code, so I’ll break it down. It’s mainly to do with the GraphQL query:
query POST_BY_SLUG($slug: String) {
mdx(slug: { eq: $slug }) {
id
slug
body
frontmatter {
date
title
}
}
}

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:
{
“data”: {
“mdx”: null
},
“extensions”: {}
}

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:
{
“slug”: “2021/03/08/third-post/”
}

Running the query again, I’ll get all the data for that file. I’ve commented out the body output for readability:
{
“data”: {
“mdx”: {
“id”: “105a5c78-6a36-56e8-976c-d53d8e6ca623”,
“slug”: “2021/01/08/third-post/”,
“body”: “function _extends() …”,
“frontmatter”: {
“date”: “2021-03-08T00:00:00.000Z”,
“title”: “Third Post!”
}
}
},
“extensions”: {}
}

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:
const {
body,
frontmatter: { title },
} = data.mdx;

An alternative way to do it would be:
const body = data.mdx.body;
const title = data.mdx.frontmatter.title;

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:
{body}

I’ll commit the changes now:
git add .
git commit -m ‘create file route API file’

Root Wrapper Concept
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:

touch gatsby-browser.js gatsby-ssr.js root-wrapper.js

The root wrapper file is where I’ll be using the wrapPageElement function:

import React from “react”;
import { Layout } from “./src/components/layout”;

export const rootWrapper = ({ element }) = > {
return {element};
};

Then, in both the gatsby-browser.js and gatsby-ssr.js files, I’ll add this:
import { rootWrapper } from “./root-wrapper”;

export const wrapPageElement = rootWrapper;

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:
import { graphql, Link as GatsbyLink } from “gatsby”;
import React from “react”;
import { Box, Heading, Link } from “theme-ui”;
– import { Layout } from “../components/layout”;

export default function IndexPage({ data }) {
return (


{data.allMdx.nodes.map(({ id, excerpt, frontmatter, slug }) = > (

{frontmatter.title}

{frontmatter.date}

{excerpt}

))}

);
};
// rest unchanged

I’ll commit the changes so far before moving on:
git add .
git commit -m ‘add root wrapper to Gatsby Browser and SSR’

404 Page
Time to make that 404 page!

touch src/pages/404.js

In the src/pages/404.js file, I’ll and add a message:
import React from “react”;
import { Box, Heading } from “theme-ui”;

export default function NotFound() {
return (

Page not found!

😢

It looks like that page doesn’t exist

);
}

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:
git add .
git commit -m ‘add 404 page’

Dark Theme Toggle
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:
import { Link as GatsbyLink } from “gatsby”;
import React from “react”;
+ import { Box, Button, Heading, Link, useColorMode } from “theme-ui”;

export const Header = ({ siteTitle, siteDescription }) = > {
+ const [colorMode, setColorMode] = useColorMode();
return (

{siteTitle}

{siteDescription}

+ {
+ setColorMode(colorMode === “default” ? “dark” : “default”);
+ }}
+ >
+ {colorMode === “default” ? “Dark” : “Light”}
+

);
};

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:
import { Link as GatsbyLink } from “gatsby”;
import React from “react”;
+import { Box, Button, Flex, Heading, Link, useColorMode } from “theme-ui”;

export const Header = ({ siteTitle, siteDescription }) = > {
const [colorMode, setColorMode] = useColorMode();
return (

+
+

{siteTitle}

{siteDescription}

+
{
setColorMode(colorMode === “default” ? “dark” : “default”);
}}
>
{colorMode === “default” ? “Dark” : “Light”}

+

);
};

Git commit before moving to the next section:
git add .
git commit -m ‘add theme toggle to header’

Code Blocks
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:

yarn add @theme-ui/prism

touch src/gatsby-plugin-theme-ui/components.js

In that file, I’ll need to define where I want to apply the Prism styles to, which is all pre and code tags:
import Prism from “@theme-ui/prism”;

export default {
pre: (props) = > props.children,
code: Prism,
};

With that defined, I’ll also need to define in the theme object which Prism theme I want to use:
// scr/gatsby-plugin-theme-ui/index.js

import { deep, swiss } from “@theme-ui/presets”;
+ import nightOwl from “@theme-ui/prism/presets/night-owl.json”;

const theme = {
…swiss,
colors: {
…swiss.colors,
modes: {
dark: {
…deep.colors,
},
},
},

styles: {
…swiss.styles,
+ code: {
+ …nightOwl,
+ },
// remainder of the file unchanged

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:
git add .
git commit -m ‘add Prism package and update theme object’

Add Components to the MDX
This next bit is ptional. 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:

touch src/components/rainbow-text.js

yarn add @emotion/react

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:
import { keyframes } from “@emotion/react”;
import React from “react”;
import { Box } from “theme-ui”;

export const RainbowText = ({ children }) = > {
const rainbow = keyframes({
“0%”: {
backgroundPosition: “0 0”,
},
“50%”: {
backgroundPosition: “400% 0”,
},
“100%”: {
backgroundPosition: “0 0”,
},
});

return (

{children}

);
};

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:

title: Third Post!
date: 2021-03-08

+ import { RainbowText } from “../../../../../src/components/rainbow-text”;

This is my third post!

> with a block quote!

+ Wheeeeeeee

And a code block:

“`js
const wheeeeee = true;
“`

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 Wheeeeeeee, I can see that animation in effect.
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:
import { MDXProvider } from “@mdx-js/react”;
import React from “react”;
import { Layout } from “./src/components/layout”;
import { RainbowText } from “./src/components/rainbow-text”;

const MDXComponents = {
RainbowText,
};

export const rootWrapper = ({ element }) = > {
return (

{element}

);
};

Now I can remove the import from the .mdx file:

title: Third Post!
date: 2021-03-08

– import { RainbowText } from “../../../../../src/components/rainbow-text”;

This is my third post!

> with a block quote!

Wheeeeeeee

And a code block:

“`js
const wheeeeee = true;
“`

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:
git add .
git commit -m ‘add component for mdx’

Markdown Images
If I want to add images to my blog posts, I can include them in the MDX files, something like this:

title: Hello World – from mdx!
date: 2021-03-06

My first post!!

## h2 Heading

![mdx logo](./mdx-logo.png)

Some meaningful prose

### h3 Heading

Some other meaningful prose

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:
yarn add gatsby-remark-images gatsby-plugin-sharp

I’ll then need to configure the plugins in gatsby-config.js:
const siteMetadata = {
title: `My Gatsby Blog`,
description: `This is my coding blog.`,
};

module.exports = {
siteMetadata,
plugins: [
`gatsby-plugin-theme-ui`,
+ `gatsby-plugin-sharp`,
+ {
+ resolve: `gatsby-plugin-mdx`,
+ options: {
+ gatsbyRemarkPlugins: [
+ {
+ resolve: `gatsby-remark-images`,
+ options: {
+ maxWidth: 640,
+ },
+ },
+ ],
+ },
+ },
+ {
+ resolve: `gatsby-source-filesystem`,
+ options: {
+ path: `${__dirname}/content/`,
+ },
+ },
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/content`,
name: `content`,
},
},
],
};

The additional gatsby-source-filesystem object is letting Gatsby know where to look for the images to be processed.
Commit this now:
git add .
git commit -m ‘add and configure images’

SEO
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:
yarn add react-seo-component react-helmet gatsby-plugin-react-helmet

I’ll need to add the gatsby-plugin-react-helmet to the gatsby-config.js plugin array:
module.exports = {
siteMetadata,
plugins: [
+ `gatsby-plugin-react-helmet`,
`gatsby-plugin-theme-ui`,
`gatsby-plugin-sharp`,
{
// rest unchanged

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:
const siteMetadata = {
title: `My Gatsby Blog`,
description: `This is my coding blog.`,
+ lastBuildDate: new Date(Date.now()).toISOString(),
+ siteUrl: `https://dummy-url-for-now.com`,
+ authorName: `Author McAuthorson`,
+ twitterUsername: `@authorOfPosts`,
+ siteLanguage: `en-GB`,
+ siteLocale: `en_gb`,
};

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:
// src/hooks/use-site-metadata.js

import { graphql, useStaticQuery } from “gatsby”;
export const useSiteMetadata = () = > {
const { site } = useStaticQuery(
graphql`
query SITE_METADATA_QUERY {
site {
siteMetadata {
title
description
+ lastBuildDate
+ siteUrl
+ authorName
+ twitterUsername
+ siteLanguage
+ siteLocale
}
}
}
`
);
return site.siteMetadata;
};

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:
import { graphql } from “gatsby”;
import { MDXRenderer } from “gatsby-plugin-mdx”;
import React from “react”;
+ import SEO from “react-seo-component”;
import { Box } from “theme-ui”;
+ import { useSiteMetadata } from “../hooks/use-site-metadata”;

export default function PostPage({ data }) {
const {
body,
+ slug,
+ excerpt,
+ frontmatter: { title, date },
} = data.mdx;
+ const {
+ title: siteTitle,
+ siteUrl,
+ siteLanguage,
+ siteLocale,
+ twitterUsername,
+ authorName,
+ } = useSiteMetadata();
return (

+

{title}

{body}

);
}

export const query = graphql`
query POST_BY_SLUG($slug: String) {
mdx(slug: { eq: $slug }) {
id
slug
body
+ excerpt
frontmatter {
date
title
}
}
}
`;

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!
import { graphql, Link as GatsbyLink } from “gatsby”;
import React from “react”;
+ import SEO from “react-seo-component”;
import { Box, Heading, Link } from “theme-ui”;
+ import { useSiteMetadata } from “../hooks/use-site-metadata”;

export default function IndexPage({ data }) {
+ const {
+ title,
+ description,
+ siteUrl,
+ siteLanguage,
+ siteLocale,
+ twitterUsername,
+ } = useSiteMetadata();
return (

+
{data.allMdx.nodes.map(({ id, excerpt, frontmatter, slug }) = > (
// rest of component unchanged

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:

yarn build

yarn serve

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:
git add .
git commit -m ‘add SEO component :sweat_smile:’

Push It to GitHub
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:
git remote add origin https://github.com/spences10/my-gatsby-blog
git branch -M main
git push -u origin main

Once you’ve put all those into the terminal and hit Enter, refresh the GitHub page to see the new project!
Deploy
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!
Vercel
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:
yarn global add vercel

I already have it installed, so now it’s a case of running the CLI command:
vc

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:
Set up and deploy “~/repos/my-gatsby-blog”? [Y/n]
Which scope do you want to deploy to?
Link to existing project? [y/N]
What’s your project’s name? (my-gatsby-blog)
In which directory is your code located? ./
> Upload [====================] 99% 0.0sAuto-detected Project Settings (Gatsby.js):
– Build Command: `npm run build` or `gatsby build`
– Output Directory: public
– Development Command: gatsby develop –port $PORT
? Want to override the settings? [y/N]

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.
Netlify
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
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:
Name: my-gatsby-blog
Branch: the default value
Build command: yarn build
Publish directory: ./public
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!
Optional Gatsby plugins
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:
Analytics
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:

Here’s the diff of root-wrapper.js:
import { MDXProvider } from “@mdx-js/react”;
import React from “react”;
+import { Helmet } from “react-helmet”;
import Layout from “./src/components/layout”;
import RainbowText from “./src/components/rainbow-text”;

const MDXComponents = {
RainbowText,
};

export const wrapPageElement = ({ element }) = > {
return (
+
+
+
+

{element}

+
);
};

Wrap!
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.

How a Custom WordPress Plugin Can Help You Manage a Website

Whether you’re a WordPress user or build websites for a living, it’s likely you’ve relied on a few plugins along the way. These additional pieces of software enable the content management system (CMS) to perform new and exciting functions. Everything from contact forms, shopping carts to SEO are just a few clicks away. Even better…