A Gatsby theme is a reusable block of a Gatsby site that can be shared, extended, and customized (source). It enables us to separate functionalities of our site to share, reuse, and modify in multiple sites in a modular way.
Early this week, Gatsby theme was announced stable! They have two official themes, the blog theme and the notes theme. They also have three starter sites (gatsby-starter-blog-theme, gatsby-starter-notes-theme, and gatsby-starter-theme) to get you started using the blog theme, notes theme, and both themes together respectively.
Using a starter site is ideal if:
- You want to get started quickly
- You don’t already have an existing site
However, I’d like to set up a Gatsby site from scratch to:
- get a better idea how the themes work, and
- see the minimum possible modifications to run a site
Follow along as I create a site, add the themes, add my own content and customizations! You can find the code for this post on my Github under the using-official-themes-without-starter
branch.
Table of contents
- Create a Gatsby site
- Install themes
- Modify theme options and metadata
- Add Markdown content and avatar image
- Shadow layout and bio components
- Customize the styles
⚠️ Note: This post describes my personal experience and perspective using the official themes for the first time. If you want to learn Gatsby themes, it’s a good idea to start from their docs and tutorial.
1) Create a Gatsby site
I do this by manually creating a minimal package.json
file in my root folder, then running yarn install
. You may also use any regular, non-theme starter site such as gatsby-starter-hello-world if you prefer.
{
"name": "eka-personal-site",
"private": true,
"description": "Personal site of @ekafyi",
"version": "0.1.0",
"license": "MIT",
"scripts": {
"build": "gatsby build",
"develop": "gatsby develop",
"start": "npm run develop",
"serve": "gatsby serve",
},
"dependencies": {
"gatsby": "^2.13.4",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}
}
2) Install themes
We are installing two official themes, gatsby-theme-blog
and gatsby-theme-notes
.
We do it the same way we install any regular Gatsby plugin; first we install the theme packages by running yarn add gatsby-theme-blog gatsby-theme-notes
.
Next, we add it to the plugins
array in gatsby-config.js
. I’m creating a new file as I’m starting from scratch; if you do this in an existing site, your config file would look different from mine. The exact content does not matter, as long as we add our themes in the plugins
like so:
// gatsby-config.js
module.exports = {
plugins: [
{
resolve: `gatsby-theme-notes`,
options: {}
},
{
resolve: `gatsby-theme-blog`,
options: {}
}
],
siteMetadata: {
title: "`Ekaʼs Personal Site`"
}
};
As you can see, I start with the most barebones config. I only have title
in my metadata and I have not modified any options yet. Let’s do that in the next step.
3) Modify theme options and metadata
How do we know what options are available to modify? I peek around and find two places where we can find that information:
- Published theme packages
- Theme files in
node_modules
At the time of writing, none of the three theme-specific starter sites provide exhaustive theme options list.
3a) Modify blog theme options
We can see the following theme options in the gatsby-theme-blog package README:
basePath
contentPath
assetPath
mdx
Let’s say we’d like to change the blog posts folder, from the default /content/posts
to /content/writing
. We can do so by passing contentPath
to the theme’s options
.
// gatsby-config.js
module.exports = {
plugins: [
// gatsby-theme-notes
{
resolve: `gatsby-theme-blog`,
// Default options are commented out
options: {
// basePath: `/`, // Root url for all blog posts
contentPath: `content/writing`, // Location of blog posts
// assetPath: `content/assets`, // Location of assets
// mdx: true, // Configure gatsby-plugin-mdx
}
}
],
// siteMetadata
};
The theme’s README also contains an additional configuration section that describes what siteMetadata
items are supported. I duly updated my config’s siteMetadata
with my name, site description, and social links.
3b) Modify notes theme options
As with the blog theme, we can find the theme options in the gatsby-theme-notes package README:
basePath
contentPath
mdx
homeText
breadcrumbSeparator
I’m going to modify the homeText
into “Home” and breadcrumbSeparator
into »
. (Note: It turns out breadcrumbs are only for Notes in subfolders, so we will not see the breadcrumb in action in this post.)
// gatsby-config.js
module.exports = {
plugins: [
{
resolve: `gatsby-theme-notes`,
// Default options are commented out
options: {
basePath: `/notes`, // Root url for all notes pages
// contentPath: `content/notes`, // Location of notes content
// mdx: true, // Configure gatsby-plugin-mdx
homeText: "Home", // Root text for notes breadcrumb trail
breadcrumbSeparator: "»", // Separator for the breadcrumb trail
}
}
// gatsby-theme-blog
],
// siteMetadata
};
You can see my full gatsby-config.js
file here.
Bonus: Theme files in node_modules
So far, the starter sites are well-documented in terms of theme options. What if we use unofficial themes with minimum info in the package README? 😕
We can assess the theme files either in the theme’s repository, or even quicker, in our project’s node_modules
folder. For instance, to see the blog theme files, we can open node_modules/gatsby-theme-blog
. There we can see the entire how the theme code actually resemble a regular Gatsby site, and what options are available.
The screenshot above shows node_modules/gatsby-theme-blog/gatsby-config.js
. We can see the options
object passed into the config and used, among others, in the gatsby-source-filesystem
plugin that looks for our content files. We also learn that if we do not define contentPath
, then content/posts
is used as default.
So—we have installed and modified our themes, but we don’t have any content yet. Let’s add them in the next step.
4) Add Markdown content and avatar image
Now we are adding our content in Markdown files. Based on the previous step, we are creating a folder called content
in my project root with three folders in it:
-
content/writing
— contain Blog Post files -
content/notes
— contain Notes files -
content/assets
— I don’t know what exactly “assets” are, so I’m going to leave this empty
I’m going to do this via the command line, though you may do so elsewhere (from Finder, Windows Explorer, or your code editor).
mkdir content content/writing content/notes content/assets
I create a short blog post in content/writing/hello-world.mdx
and a note in content/notes/hello-note.mdx
. You can see my content
folder here.
So far, we have: installed the theme, modified theme options, and added content. Is it possible to have a site running without even a src
folder? We’re going to find out as I run the site for the first time.
I run gatsby develop
and got the following error:
There was an error in your GraphQL query:
- Unknown field 'childImageSharp' on type 'File'.
File: node_modules/gatsby-theme-blog/src/components/bio.js
I open the offending component and discover that we are required to have a PNG/JPG/GIF image file called avatar
.
// node_modules/gatsby-theme-blog/src/components/bio.js
const bioQuery = graphql`
query BioQuery {
site {
siteMetadata {
author
}
}
avatar: file(absolutePath: { regex: "/avatar.(jpeg|jpg|gif|png)/" }) {
childImageSharp {
fixed(width: 48, height: 48) {
...GatsbyImageSharpFixed
}
}
}
}
`
I peek at the blog theme starter and see that we should have the avatar image in our content/assets
folder. I duly add a (badly, faux-artsy coloured selfie) avatar there and re-run the app. Aaaand… it works!
The site title, avatar image, and the social links correctly point to mine. I’ve got a site running without even having a src
folder! 😯
However, there are several issues:
- The bio text still uses the default (it’s not mentioned in the theme’s README or the starter 😕)
- The
/notes
directory does exist and show my Notes content, but it’s not linked from the header navigation
Next we’re going to “shadow” the components to solve those issues.
5) Shadow layout and bio components
Component Shadowing is a technique that allows us to override a theme’s components without directly modifying or forking the theme.
Now we are going to shadow three components:
- Blog theme’s bio text -> to use my own bio text
- Blog theme’s header -> to add “Notes” link to the navigation
- Note theme’s layout -> so it matches the rest of the site (ie. matches the Blog pages)
For the second and third components, I copy from the gatsby-starter-theme as it seems to be the fastest way!
5a) Shadow Blog theme’s bio component
I first check the Blog theme’s bio.js
component, but turns out it renders another component called <BioContent>
. I open bio-content.js
and yes, that’s our culprit.
Steps to shadow a theme’s file:
- Create a folder with the theme name in our
src
folder-
Example: To shadow
gatsby-theme-blog
, I create the foldersrc/gatsby-theme-blog
-
Example: To shadow
- Create the component file in the folder above with the file/folder structure resembling the theme’s structure after
src
-
Example: The original file we want to shadow is
node_modules/gatsby-theme-blog/src/components/bio-content.js
. We copycomponents/bio-content.js
into our theme folder from the step above. Hence our file is insrc/gatsby-theme-blog/components/bio-content.js
.
-
Example: The original file we want to shadow is
TL;DR version, relative from our project root:
- Original:
node_modules/gatsby-theme-blog/src/components/bio-content.js
- Shadow:
src/gatsby-theme-blog/components/bio-content.js
I create a simple file duplicating the original bio-content.js
with the Bio text changed.
// src/gatsby-theme-blog/components/bio-content.js
import React, { Fragment } from "react"
export default () => (
<Fragment>
Personal site of Eka, front-end web developer and competitive napper.
</Fragment>
)
I restart the app and now it shows my bio text. 👌🏾
5b) Shadow Blog theme’s header component
For the header component, if I were to do what I did with the bio component (ie. export a new component), I’d be overriding the entire header.
// src/gatsby-theme-blog/components/header.js
import React, { Fragment } from "react"
export default () => (
<Fragment>
My custom header <br/>
The entire header is gone! 😱
</Fragment>
)
It’s not what I want because for now I’m happy with the site title, dark mode toggle button (both UI and functionality), and the bio; all I want to do is to add a link to the Notes page.
Here we can see that shadowing is more than just overriding a component. We can also interact with the theme’s component, along with its original props, as needed.
As shown in the Blog theme’s header.js
, the <Header>
component accepts children
prop between the site title and the dark mode switch, where we can pass our content.
Now we’re going to: (1) create the shadowing file in our site, (2) import the header component, and (3) render the header with our custom children
.
// src/gatsby-theme-blog/components/header.js
import React from "react";
import Header from "gatsby-theme-blog/src/components/header";
export default props => {
return (
<Header {...props}>
<div style={{ color: "red" }}>My custom header</div>
</Header>
);
};
It works—I can add my own content without having to rewrite the entire header component! 💃🏽
You can also pass props to the component (provided the component supports it). For instance, here I modify the title
prop into “My Custom Title”.
// src/gatsby-theme-blog/components/header.js
import React from "react";
import Header from "gatsby-theme-blog/src/components/header";
export default props => {
return (
<Header {...props} title="My Custom Title">
<div style={{ color: "red" }}>My custom header</div>
</Header>
);
};
Here is the result.
Finally, I’m going to add a link to the Notes page with the code from gatsby-starter-theme/header.js. Here we use functionalities from Theme UI, a theming library used by the Blog theme. In a nutshell, Theme UI’s Styled
component and css
prop allow us to use HTML element with the theme’s theme-ui
styles, for example to match the theme’s heading
font family. Styled
also supports the as
prop (popularized by libraries like Emotion and Styled Component), so we can take advantage of Gatsby’s built-in routing through the Link
component with <Styled.a as={Link}>
(meaning: use <Link>
component with <a>
style).
import React from "react";
import { Link } from "gatsby";
import { css, Styled } from "theme-ui";
import Header from "gatsby-theme-blog/src/components/header";
export default props => {
return (
<Header {...props}>
<Styled.a
as={Link}
to="/notes"
css={css({
// styles
})}
>
Notes
</Styled.a>
</Header>
);
};
It works! You can see the full code here.
5c) Shadow Note theme’s layout component
We already have a Notes page at /notes
(ie. localhost:8000/notes), but it does not have the header and footer yet. That’s because this view comes from the Notes theme, separate from the Blog theme, which renders the header and footer.
Now we are going to shadow the Layout component in src/gatsby-theme-notes/components/layout.js
, import the Blog theme’s Layout component, and wrap our content in the latter.
As with the previous step, the shadowing component in our site gets the props from the original component (ie. Notes theme’s Layout), so we can wrap the entire props.children
(ie. Notes content) without having to rewrite anything else.
// src/gatsby-theme-notes/components/layout.js
import React from "react"
import BlogLayout from "gatsby-theme-blog/src/components/layout"
export default props => <BlogLayout {...props}>{props.children}</BlogLayout>
Restart the app, and voila, the Blog theme layout (header and footer) now applies to the Notes section, too!
6) Customize the styles
Unless you happen to like the theme’s default purple, in all likelihood you would want to modify your theme-powered site’s visual styles such as colours and typography.
The Blog theme uses the theming library we discussed briefly above, Theme UI. Theme UI itself works as a “theme plugin” that exports a theme
object from gatsby-theme-blog/src/gatsby-plugin-theme-ui
. Check out Theme UI’s docs to read more about the theme
object.
The Blog theme breaks down the theme-ui
object into separate files (colors, components, etc) that are imported in the gatsby-plugin-theme-ui
index file. Accordingly, if we only want to customize the colors, we can shadow the colors.js
file, and so on.
We customize the styles by shadowing the gatsby-plugin-theme-ui
file(s) the same way we shadow any other components. To shadow node_modules/gatsby-theme-blog/src/gatsby-plugin-theme-ui/colors.js
, for example, we take the part after src
(gatsby-plugin-theme-ui/colors.js
) and put it in our shadowing folder, src/gatsby-theme-blog
. Thus, we create our file at src/gatsby-theme-blog/gatsby-plugin-theme-ui/colors.js
.
Now we’re going to modify the colors, using the Blog theme starter’s file as reference. As we don’t want to replace all the colors, we import the theme’s default theme colors and merge them with our modified colors. We also import lodash’s merge
to deep-merge the style objects. It’s not required but it helps us do the deep merge; we may omit it if we want to code the deep merge ourselves OR if we don’t need to merge with the default theme (ie. we rewrite the entire theme styles).
// src/gatsby-theme-blog/gatsby-plugin-theme-ui/colors.js
import merge from "lodash.merge";
import defaultThemeColors from "gatsby-theme-blog/src/gatsby-plugin-theme-ui/colors";
export default merge({}, defaultThemeColors, {
text: "rgba(0,0,0,0.9)",
primary: "#0e43c5",
background: "#fff1c1",
modes: {
dark: {
text: "rgba(255,255,255,0.9)",
primary: "#f7e022",
background: "#151f48"
}
}
});
Other theme styling attempts:
-
gatsby-plugin-theme-ui/typography.js
- Result: ✅❌ Partial success. I could change
fonts.body
from the default Merriweather typeface to system-ui, but I could not changefonts.heading
. It’s likely because thefonts.heading
value is overridden into Montserrat ingatsby-plugin-theme-ui/index
. Which brings us to…
- Result: ✅❌ Partial success. I could change
-
gatsby-plugin-theme-ui/index.js
- Result: ❌ Fail. My shadowing
index.js
does not seem to get detected. I test by runningconsole.log(‘Hello’)
, which did not get printed.
- Result: ❌ Fail. My shadowing
-
gatsby-plugin-theme-ui/styles.js
- Result: ✅ Success! I modify the hover link style to add underline and use the
secondary
color.
- Result: ✅ Success! I modify the hover link style to add underline and use the
You can see those three files here.
Note about theme order: If multiple themes use theme-ui
, the last theme specified in the plugins
array in our gatsby-config.js
wins.
This is the end result of the steps in this post.
Conclusion
Here are my impressions after trying the official themes.
- Themes help you get started building a simple, basic Gatsby site quickly without even needing a
src
folder. More advanced users can take advantage of themes to create modular, extendable, composable blocks of their site (though I have not personally got to this point). - The official themes are a good place to start using, modifying (through shadowing), and dissecting themes.
- The difficulty level of using and shadowing themes highly depends on the theme’s documentation, eg. what options are available, what data are required.
Do you have examples of non-official themes that you build and/or use? Let me know in the comments!
Next stop, learn to do more advanced customizations and/or build my own theme. Thanks for reading, til next post! 👋🏾
Top comments (1)
thanks a lot!! helped me setup and understand