DEV Community

Eka
Eka

Posted on

Using and customizing official Gatsby themes from scratch without starter sites

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

  1. Create a Gatsby site
  2. Install themes
  3. Modify theme options and metadata
  4. Add Markdown content and avatar image
  5. Shadow layout and bio components
  6. 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:

  1. Published theme packages
  2. 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!

A standard blog site with the heading title “Eka’s Personal Site”, my avatar, a default bio section saying “Change me”, and one post entry titled “Hello world”. On the top right there is a toggle button with the sun icon. When the button is clicked, dark mode is activated and the toggle button now has moon icon.

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 folder src/gatsby-theme-blog
  • 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 copy components/bio-content.js into our theme folder from the step above. Hence our file is in src/gatsby-theme-blog/components/bio-content.js.

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!

Before (left) and after (right)

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
  • gatsby-plugin-theme-ui/index.js
    • Result: ❌ Fail. My shadowing index.js does not seem to get detected. I test by running console.log(‘Hello’), which did not get printed.
  • gatsby-plugin-theme-ui/styles.js
    • Result: ✅ Success! I modify the hover link style to add underline and use the secondary color.

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! 👋🏾

Oldest comments (1)

Collapse
 
thulasiram profile image
Thulasi Ram

thanks a lot!! helped me setup and understand