Index

Building a blog with Gatsby v2 and TypeScript

August 31, 2018

I plan on using this blog to discuss web technologies that I find interesting.

My first topic is this web site itself.

I build this blog as a static site using:

  1. Gatsby v2 as a static generator
  2. TypeScript, as much as possible, because it’s awesome and makes it so much easier to write working code
  3. Material UI for layout, because I’m not a designer and it makes my life so much easier by giving me a bunch of widgets

The source is here.

TypeScript with Gatsby

Setting up TypeScript to hook into Gatsby is almost trivial. Merely install the gatsby-plugin-typescript module to your project and then add it to your gatsby-config.js file:

{
  plugins: [
    `gatsby-plugin-typescript`,
  ]
}

Using TypeScript with React

Gatsby is a React framework. It’s a little tricky to get started using TypeScript with React because you have to figure out the appropriate typings for common object.

I mostly found myself mostly using function components. The appropriate typing is:

interface Properties {
    key: string
}

const component: React.SFC<Properties> = props => (
  <div>{props.key}</div>
);

A full component is similar, but with an additional slots for state (as well as for snapshot, whatever that is…):

interface Properties {
    propKey: string
}

interface State {
    stateKey: string
}

class Component extends React.Component<Properties, State> {
    constructor (props: Properties) {
        super(props);
        this.state = { stateKey: 'abc' }
    }

    render () {
        return (
            <div>{this.state.stateKey}:{this.props.propKey}
        )
    }
}

I’m not sure why we need to explicitly give the type for props but otherwise the type is any.

Honestly, that’s mostly it for the TypeScript+React combination.

Material UI

Using TypeScript with Material UI is a bit trickier. The actual Material UI typings are good but it requires a little bit of fancy work to apply them in a type-safe way.

The principle complications emerge because of figuring out how to use Material UI’s CSS solution. Material UI uses JSS (a type of CSS-in-JavaScript) under the covers to generate component-specific CSS classes.

For example, suppose we want a list (I will use basic html ul, li elements, rather than Material UI’s List component for simplicity) with a Button:

<head>
  <style>
   .myContainer {
     display: flex;
     justify-content: center;
     /* ... */
   }
   /* ... */
  </style>
</head>
<body>
  <div class="myContainer">
    <ul class="myList">
      <li>Item 1</li>
      <li>Item 2</li>
    </ul>
    <Button class="myButton">My Button</Button>
  </div>
</body>

The core of this construct would just be a React component with these elements:

const myComponent = props => (
  <div>
    <ul>
      <li>Item 1</li>
      <li>Item 1</li>
    </ul>
    <Button>My Button</Button>
  </div>
);

Of course, this lacks both the styles and type-safety.

To add the styles, we merely wrap myComponent via a higher-order component withStyles, passing in a styles object. Thus:

// myComponent.jsx

const styles = {
  root: {
    display: 'flex',
    justifyContent: 'center'
    /* ... */
  },
  /* ... */
}

const myComponent = props => {
  const { classes } = props;
  return (
    <div className={classes.root}>
      {/* ... */}
    </div>
  );
}

export default withStyles(styles)(myComponent)

As you can tell, withStyles adds an object called classes to the component’s props with a key for the defined styles. We define the styles themselves as a plain old javascript object, with the CSS properties converted to camelCase so that we don’t need to quote the names and the values are strings with the standard CSS values (or, if given as numbers, interpreted as pixels).

You can a lot of cool features with styles out of the box with Material UI, like nested styles or media queries. For example:

const styles = {
  root: {
    '& a': {
      color: 'blue'
    }
  },
  '@media(min-width: 500px)': {
    root: {
      flex: 'display'
    }
  }
}

/* ... */

will get compiled into a style-sheet entry like

<style>
  .myComponent-root-1 a {
    color: blue;
  }
  '@media(min-width: 500px) {
    .myComponent-root-1 {
      flex: display;
    }
  }
</style>

In fact, since Material UI ultimately just uses JSS, you can add other JSS plugins to get virtually unlimited power (though I haven’t actually tried this).

There’s still one missing aspect, namely type-safety of the styles functionality. Material UI provides a helper function createStyles which behaves functionally (i.e. once it’s compiled to JavaScript) as the identity function but which provides two areas of type safety:

  • we get some type checking of CSS properties, so that a style like {display: 0 } is disallowed because the argument of display cannot be a length. Unfortunately, type checking of many/most property values ends up rather weak (this is apparently a known issue in csstype that ultimately comes from the oddities of the CSS spec)
  • the keys in the classes property match those given in styles.

We then use the helper WithStyles to add the appropriate classes properties to the component. Thus, the final product looks like:

// myComponent.tsx

const styles = createStyles({
  root: {
    display: 'flex',
    justifyContent: 'center'
    /* ... */
  },
  myList: {
    /* ... */
  },
  myButton: {
    /* ... */
  }
  /* ... */
})

const myComponent: React.SFC<
  WithStyles<typeof styles>
> = props => {
  const { classes } = props;
  return (
    <div className={classes.root}>
      <ul className={classes.myList}>
        <li>Item 1</li>
        <li>Item 2</li>
      </ul>
      <Button className={classes.myButton}>My Button</Button>
    </div>
  );
}

export default withStyles(styles)(myComponent)

Server-side rendering (SSR)

Getting server-side rendering (SSR) working (i.e. so the initial page load from the server is an html file, then future page loads use JavaScript) was a bit of a pain. First of all, I had no experience with it. However, the example for Gatsby provided by material UI is a bit fragile and/or not yet fully set up for Gatsby v2.

The main moving component is the CSS-in-JS library JSS. Components have to register their styles with a Material UI provider MuiThemeProvider that knows the theme and that handles the style sheet entries (see my MuiRoot.tsx). On the server side, we apparently need an extra JssProvider component, which provides a way of turning the accumulated styles into a string <style> tag, as far as I can tell. For the SSR to work properly, a particular construct (the “sheets manager” needs to be the same in both the MuiThemeProvider and the JssProvider). Thus, I changed the Material UI example so that this property is immediately and directly passed in the MuiThemeProvider component, rather than passing it in a property and kind-of hoping it makes it to the correct compoent.

It was a bit of a pain, but it works now!

(If you want to see the server-side rendering in action, disable javascript, e.g. via Chrome developer tools and reload my homepage. It should look exactly the same as when javascript is enabled.)

To improve

  • I would like to use setup comments using something like Staticman.

  • Some slightly improved styling, e.g. for inline code

Conclusion

I’m rather happy with how this page turned out. Using React for more complicated page architecting and using markdown to write blog posts is quite an effective combination.


Jonathan Ganc
A blog about web development, with a back end focus, written by Jonathan Ganc