a warm space

Building the new fathom site

2019-02-17

Finally, after much well intentioned criticism from friends, family, and coworkers, we fixed up the fathom site.

The old site was built to get it live fast. And initially that worked really well. It let me (and eventually the larger fathom team) write content and blog posts easily, and iterate quickly. But as we wanted to get more "features" on there, the blog, the whitepaper, etc, it grew in complexity.

It also was, to put it one way, kinda ugly. A new user getting to the site was greeted with a wall of text, and no way to get their bearings. It worked for some people, but it definitely turned away others.

With the new site I wanted to keep what made the old one great, simplicity and honesty, but make it easier to parse and more accessible.

Here I just want to talk a little bit about what we used to build it and the experience of putting it together.

Next JS

The site is built using NextJS. I was initially turned off by Next as it bills itself as a Server Side Rendering (SSR) framework, while I was looking for a static site generator, that I could just throw up on Git(hub/lab) Pages or Netlify. However, it turned out that Next did have the abilty to export static pages, and since I'm currently on a zeit.co trip, I decided to give it a shot.

Pages

Next uses a file based routing system, so every file in a pages folder, corresponds to a different page on your site.

Our pages folder looks like this:

pages
├── about.mdx
├── blog
│   └── index.mdx
├── community.mdx
├── credentials.mdx
├── _document.js
├── fns.mdx
└── index.mdx

This makes it incredibly fast to set up a new page on your site, just add a new file. The downside is it gets more complicated if you want to have a more complicated routes system. Lucky for us we don't, so it's perfect.

Dev Server

The thing that really makes Next wonderful though is the developer experience around it. It's dev server, the thing that serves your site as you work on it, is simply the best out of any I've used. It has hot module replacement, instant page reloads, and handles changes in the filesystem like adding or renaming files perfectly.

It's also painlessly extensible via a plugins that let you add typescript support, or as you might've seen earlier, MDX files.

MDX

MDX is actually what triggered a complete rewrite of the site, instead of sticking with our previous build system (which was similar to this site).

It's a combination of markdown, a markup lanauge for rich text, and JSX, a syntax extension to javascript that lets you neatly describe react component trees in an HTML like language.

I'm a big fan of markdown. It's near ubiqitous on the web, and it's highly readable unrendered, but it's really only good at writing natural language documents. If you want to include different elements on the page, you're pretty much out of luck.

On the opposite end I've never really been a fan of JSX. I've never really liked html syntax, especially for writing content, and I'd rather just stick to Javascript for dealing with JavaScript-y things.

However, JSX + Markdown is hugely appealing for me. JSX makes sense as a template language, to describe the structure of your page, but markdown is much better at actually writing copy, and so they neatly address each other's flaws.

Underneath it all it's also just react, javascript components that can be described as simple functions. This gives it a massive leg up on other template solutions that require you to learn a new syntax, or to internalize how they parse files and inject structure. Chances are you have some experience with JS or React and MDX let's you leverage that directly.

Problems I ran into

All was not smooth sailing. I did run into some problems that had less than obvious solutions.

Index pages and filesystem parsing

By far the hardest one to solve, which I hoped would be easier, was generating an index page for our blog files. You can see the result here. It looks pretty simple but it was anythig but.

The issue is that in order to generate an index I need to have a list of all the files in the blog folder. But because I'm targetting the browser with static export I can't just use fs. I needed something that could parse the filesystem at build time.

Other solutions I'd seen involved a second build step, where you have a script that parse your blog posts and generates a json file that describes them, which you then use to create the index. Doing this loses some of the magic of Next, where instead of the fast seamless reload when you make changes, for a specific kind of change you have to remember to go run a different script.

I did some digging in the code for Next's own site and found require.context.

const previewItems = importAll(
  require.context('../../blog', false, /-preview.mdx$/)
)

It took me a long while to actually parse this and figure out what's happening.

require.context is webpack feature which is think is to allow dynamic requires. I don't think it's meant to be used by end users, and is more for projects like Next building tooling on top of Webpack. But it fits our needs perfectly as it allows us to generate a list of files in a directory at build time!

We end up with something like this

export const Posts = importAll(
  //@ts-ignore
  require.context('../../pages/blog/archive', true, /(?!index).mdx$/)
);

You can tell it's wierd by that ts-ignore :) It works though!

Getting Next to play nice with typescript

This wasn't the only case of TS wrangling with Next.

While getting Next to parse and use typescript files was a breeze I did run into some problems getting next to play nice with the typings of react-hyperscript.

I was trying to use Next's link component, which let's you do neat things like prefetching, which makes your site feel crazy fast. The component expects an <a> component as it's child, but it's typing for the children prop, was slightly different than what react-hyperscript was using.

This meant that instead of doing this:

h(Link, {href: '/about'}, [h('a', 'about')

I had to do this:

h(Link, {href: '/about', children: h('a', 'about')})

Not a huge deal, but it was kind of a pain to figure out :/

Using styled components

The last problem I ran into was with styled components. They make it crazy straightforward to isolate your styles, avoid css inheritance hell, and keep all your presentational logic tightly coupled.

They also work perfectly with Next's static export, outputting a stylesheet and generating class names for components.

However it was slightly less straightforward to use when Next is using SSR while you're running the dev server.

To do that you have to use a custom _document.js which is what Next uses to wrap your app to enable SSR.

static getInitialProps ({ renderPage }) {
  const sheet = new ServerStyleSheet()
  const page = renderPage(App => props => sheet.collectStyles(<App {...props} />))
  const styleTags = sheet.getStyleElement()
  return { ...page, styleTags }
}

This funciton get's the stylesheet from your app, and then you can use this.props.styleTags in your exported html to render the stylesheet properly!

Was it worth it?

To answer this question we gotta dig into a little bit what's important to us here.

You may have noticed I have a bit of a habit of writing about static site generators on this site (making it a meta static site). This isn't just because I'm a huge nerd, but because I think the way we create our websites has an impact on things far beyond just their appearance.

Creating them

For one how easy it is to extend and modify your site impacts how much you'll actually do that. I want a site that I can freely build features on top of in a maintainable way. If I want to include a graph in a blog post, or even an interactive chart, I want to be able to just do it, not to have to write out some purpose built script to implement the ability to add custom javascript to the page.

And on the flipside if I'm just tweaking the wording in a blog post I want to be able to see the results immediately.

For community based projects (like fathom) this extends to anyone who wants to contribute to the site, not just me. The site should be easy for anyone to contribute to.

Fast and neutral to hosts

Importantly the resulting site, the html, js and css that's actually downloaded to a viewers browser, should be small, fast, and as simple as possible. It should be able to served from any static site host. This means that it's firmly in the creators control and they're not locked in to any space.

Don't forget to have fun

And finally, it should be fun. Building sites is an excersise in creativity and it should feel like it. The tooling you use should bring you joy and get our of your way.

On this criteria (and all the others) the Next+MDX combo does fantastically. It feels like a stable foundation that does what you need it to and no more, and lets you just get to the fun, and important part, of creating, tweaking, and publishing your site.

Honestly, it's only a matter of time before I redo this site with the same stack :D

Have some thoughts or feedback? Shoot me a comment here!



show preview