How to create a resume with Gatsbyjs

Online resume

There are a lot of reasons to use a static site generator to generate your resume online:

  • it is always nice to have a fast loading time
  • you don’t want to take any chance with the site being down when a prospective employer accesses your resume
  • it is cheap to deploy: the load on the page should be low, so a free hosting solution like Netlify makes sense

For all those reasons, Gatsby makes a good choice as a platform to publish your resume online.

This article will do exactly that:

Resume definition format

You will update the content of your resume regularly, so you really don’t want to sprinkle this content inside a static HTML template.

The JSON Resume is an open-source project to create a JSON-based standard for resumes.

Personally, I find that a JSON file is more difficult to update than a yaml file, so we’ll use the format defined by the JSON resume project, but in a YAML format.

The project comes with a sample resume, from the fictional character Richard Hendriks from the excellent series “Silicon Valley” on HBO:

https://github.com/jsonresume/resume-schema/blob/v1.0.0/examples/valid/complete.json

Using a YAML format, the resume looks like this:

---
basics:
  name: Richard Hendriks
  label: Programmer
  image: ''
  email: richard.hendriks@mail.com
  phone: "(912) 555-4321"
  url: http://richardhendricks.example.com
  summary: Richard hails from Tulsa. He has earned degrees from the University of
    Oklahoma and Stanford. (Go Sooners and Cardinal!) Before starting Pied Piper,
    he worked for Hooli as a part time software developer. While his work focuses
    on applied information theory, mostly optimizing lossless compression schema of
    both the length-limited and adaptive variants, his non-work interests range widely,
    everything from quantum computing to chaos theory. He could tell you about it,
    but THAT would NOT be a “length-limited” conversation!
  location:
    address: 2712 Broadway St
    postalCode: CA 94115
    city: San Francisco
    countryCode: US
    region: California

Gatsby setup

To install Gatsby, you start with installing the 3 required packages

yarn init -y
yarn add gatsby react react-dom

You can then add the usual scripts in your package.json file to start the Gatsby server:

"scripts": {
  "build": "gatsby build",
  "clean": "gatsby clean",
  "develop": "gatsby develop"
},

At this stage, you can already start Gatsby with yarn develop, but there is nothing available for rendering.

Loading the resume in Gatsby

The gatsby-source-filesystem plugin allows you to load any file in Gatsby.

yarn add gatsby-source-filesystem

To configure this plugin, you create a gatsby-config.js file where you tell the plugin which directory to parse to read files.

module.exports = {
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `./resume`,
      },
    },
  ],
}

You can then start Gatsby (yarn run develop) and load the Graphiql server, usually at that URL: http://localhost:8000/___graphql

The gatsby-source-filesystemwill populate the allFile root with the files it found, as shown by this query:

query MyQuery {
  allFile {
    edges {
      node {
        absolutePath
      }
    }
  }
}

the results will show your resume file:

{
  "data": {
    "allFile": {
      "edges": [
        {
          "node": {
            "absolutePath": "<path to>/resume/resume.yaml"
          }
        }
      ]
    }
  }
}

Exploration

The step here is not required, but it shows how you could write your own plugin to parse the resume.

In the gatsby-node.js file, you could write a very basic plugin to read the content of the resume, using the onCreateNode method to later create new nodes to expose your resume in the GraphQL API:

async function onCreateNode({ node, loadNodeContent }) {
  if (node.internal.mediaType !== `text/yaml`) {
    return;
  }

  const content = await loadNodeContent(node);
  console.log(`File: ${node.absolutePath}:`);
  console.log(content);
}

exports.onCreateNode = onCreateNode;

When you restart the server, you can see the content of the resume file on screen:

success onPreBootstrap - 0.009s
success createSchemaCustomization - 0.002s
File: <path to>/resume/resume.yaml:
---
basics:
  name: Richard Hendriks
  label: Programmer

Interesting but there are existing plugins who already do that … and much more.

Transformer plugin: gatsby-transformer-yaml

This plugin is a good candidate for us:

  • it reads the yaml file
  • parses it
  • makes it available if the GraphQL API

You install that plugin with:

yarn add gatsby-transformer-yaml

and the magic happens when you restart the server: 2 new root types appear in the Graphiql interface, allResumeYaml and resumeYaml.

If we suppose that the site will contain only one resume, we can get the content of the resume using that query:

query MyQuery {
  resumeYaml {
    basics {
      email
      name
      phone
    }
  }
}

and, we get back:

{
  "data": {
    "resumeYaml": {
      "basics": {
        "email": "richard.hendriks@mail.com",
        "name": "Richard Hendriks",
        "phone": "(912) 555-4321"
      }
    }
  }
}

Time to render some data

Let’s create our first page:

  • you can delete the gatsby-node.js file containing our exploration plugin.
  • create a src/pages/index.js

We’ll start with the most basic data extraction (name and email) to make sure that all is in order.

In src/pages/index.js :

import React from 'react';

const Resume = ({ data }) => {
  const resume = data.resumeYaml;
  const { basics } = resume;
  return (
    <React.Fragment>
      <h1>{basics.name}</h1>
      <h2>{basics.email}</h2>
    </React.Fragment>
  );
};

export default Resume;

export const query = graphql`
  query MyQuery {
    resumeYaml {
      basics {
        email
        name
      }
    }
  }
`;

This will give us our first page:

Not quite enough yet to get you a job at Hooli, but it’s a promising start.

Resume rendering

The JSON resume project contains also a library of themes to render resume following the specification.

For the purpose of this presentation, I have chosen the flat theme by Mattias Erming, because

Process to build the resume

I won’t describe all the steps to port that template to Gatsby, just the major steps

extra package: gatsby-react-helmet

Helmet is nice addition because it makes it very easy to:

  • to set page title
  • add external css dependencies

You install that package with:

yarn add gatsby-plugin-react-helmet react-helmet

and add it to your gatsby-config.js file.

issue with CSS ordering

The way you add a specif css document (the one coming with the flat theme example), is by importing if in the gatsby-browser.js document:

import "./src/styles/style.css"

The style requires 2 other CSS loaded from a CDN, and you can use Helmet to do that:

<Helmet>
  <title>{basics.name}</title>
  <meta name="description" content={`resume for ${basics.name}`} />
  <link
    rel="stylesheet"
    href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.2.0/css/bootstrap.min.css"
  />
  <link
    rel="stylesheet"
    href="https://cdnjs.cloudflare.com/ajax/libs/octicons/2.0.2/octicons.min.css"
  />
</Helmet>

The problem is that the local style loaded by gatsby-browser.js is loaded before the styles loaded by Helmet and that broke the rendering: for example the H1..4 styles were the ones defined by bootstrap, and not the local styles file.

The only solution I found, was to load bootstrap as a dependency and load it from gatsby-browser.js, before the local style:

import 'bootstrap/dist/css/bootstrap.css'
import "./src/styles/style.css"

porting process

The process involved updating the GraphQL query to import more and more data (the minimal theme only omits the projects section of the resume).

The final query asks for all the data:

query MyQuery {
    resumeYaml {
      basics {
        email
        name
        label
        phone
        url
        summary
        profiles {
          network
          url
          username
        }
      }
      work {
        description
        endDate(formatString: "MMM, YYYY")
        highlights
        location
        name
        startDate(formatString: "MMM, YYYY")
        position
        summary
        url
      }
      volunteer {
        endDate(formatString: "MMM, YYYY")
        highlights
        organization
        position
        startDate(formatString: "MMM, YYYY")
        summary
        url
      }
      education {
        area
        courses
        endDate(formatString: "MMM, YYYY")
        gpa
        institution
        startDate(formatString: "MMM, YYYY")
        studyType
      }
      awards {
        awarder
        date(formatString: "MMM, YYYY")
        summary
        title
      }
      publications {
        name
        publisher
        releaseDate(formatString: "MMM, YYYY")
        summary
        url
      }
      skills {
        keywords
        level
        name
      }
      languages {
        fluency
        language
      }
      interests {
        keywords
        name
      }
      references {
        name
        reference
      }
    }
  }

and each section of the resume has its own component:

const Resume = ({ resume }) => {
  const { basics, work, volunteer, education, awards, publications, skills, languages, interests, references } = resume;
  return (
<React.Fragment>
  <Helmet>
    <title>{basics.name}</title>
    <meta name="description" content={`resume for ${basics.name}`} />
    <link
      rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/octicons/2.0.2/octicons.min.css"
    />
  </Helmet>
  <Header basics={basics} />
  <div id="content" className="container">
    <Contact basics={basics} />
    <About basics={basics} />
    <Profiles profiles={basics.profiles||[]} />
    <Work works={work} />
    <Volunteer volunteers={volunteer} />
    <Education educations={education} />
    <Awards awards={awards} />
    <Publications publications={publications} />
    <Skills skills={skills} />
    <Languages languages={languages} />
    <Interests interests={interests} />
    <References references={references} />
  </div>
</React.Fragment>
);

Pretty easy to read and update.

Deployment

We won’t describe here the actual deployment as there are a lot of articles out there describing how to deploy a Gatsby site.

For instance, the site https://resume-with-gatsby.netlify.com/ was deployed on Netlify with a couple of clicks.