Choosing a tech stack for my personal dev blog in 2020

Colin McDonnell @colinhacks

updated August 11th, 2020

Check out the HN roast discussion here! πŸ€—

This article has been translated into Russian.

I recently set out to build my personal website β€” the one you're reading now, as it happens!

Surprisingly, it was harder than expected assemble a "tech stack" that met my criteria. My criteria are pretty straightforward; I would expect most React devs to have a similar list. Yet it was surprisingly hard to put all these pieces together.

Given the lack of a decent out-of-the-box solution, I worry that many developers are settling for static-site generators that place limits on the interactivity and flexibility of your website. We can do better.

tl;dr
Clone the repo here to get started with this setup: https://github.com/colinhacks/devii.

Let's quickly run through my list of design goals:

React (+ TypeScript)

I want to build the site in React and TypeScript. I love them both wholeheartedly, I use them for my day job, and they're gonna be around for a long time. Plus writing untyped JS makes me feel dirty.

I don't want limitations on what my personal website can be/become. Sure, at present my site consists of two simple, static blog posts. But down the road, I may want to build a page that contains an interactive visualization, a filterable table, or a demo of a React component I'm open-sourcing. Even something simple (like the email newsletter signup form at the bottom of this page) was much more pleasant to implement in React; how did we use to build forms again?

Plus: I want access to the npm ecosystem and my favorite UI, animation, and styling libraries. I sincerely hope I never write another line of raw CSS ever again.

Good authoring experience

If it's annoying to write a blog post, I won't do it. That's a regrettable law of the universe. Even writing blog posts in plain HTML β€” a bunch of <p> tags in a div β€” is just annoying enough to bug me. The answer: Markdown of course!

Static site generators (SSGs) like Hugo and Jekyll provide an undeniably wonderful authoring experience. All you have to do is touch a new .md file in the proper directory and get to writing. Unfortunately all Markdown-based SSGs I know of are too restrictive. Mixing React and Markdown on the same page is either impossible or tricky. If it's possible, it likely requires some plugin/module/extension, config file, blob of boilerplate, or egregious hack. Sorry Hugo, I'm not going to re-write my React code using React.createElement like it's 2015.

Well, that doesn't work for me. I want my website to be React-first, with a sprinkling of Markdown when it makes my life easier.

Static generation

As much as I love the Jamstack, it doesn't cut it from an SEO perspective. Many blogs powered by a "headless CMS" require two round trips before rendering the blog content (one to fetch the static JS bundle and another to fetch the blog content from a CMS). This degrades page load speeds and user experience, which accordingly degrades your rankings on Google.

Instead I want every page of my site to be pre-rendered to a set of fully static assets, so I can deploy them to a CDN and get fast page loads everywhere. You could get the same benefits with server-side rendering, but that requires an actual server and worldwide load balancing to achieve comparable page load speeds. I love overengineering things as much as the next guy, even I have a line. πŸ˜…

My solution

I describe my final architecture design below, along with my rationale for each choice. I distilled this setup into a website starter/boilerplate available here: https://github.com/colinhacks/devii. Below, I allude to certain files/functions I implemented; to see the source code of these, just clone the repo git clone [email protected]:colinhacks/devii.git

Next.js

I chose to build my site with Next.js. This won't be a surprising decision to anyone who's played with statically-rendered or server-side rendered React in recent years. Next.js is quickly eating everyone else's lunch in this market, especially Gatsby's (sorry Gatsby fans).

Next.js is by far the most elegant way (for now) to do any static generation or server-side rendering with React. They released their next-generation (pun intended) static site generator in the 9.3 release back in March 2020. In the spirit of using technologies in the spring of their life, Next.js is a no-brainer.

Here's a quick breakdown of the project structure. No need to understand every piece of it; but it may be useful to refer to throughout the rest of this post.

.
β”œβ”€β”€ README.md
β”œβ”€β”€ public // all static files (images, etc) go here
β”œβ”€β”€ pages // every .tsx component in this dir becomes a page of the final site
|   β”œβ”€β”€ index.tsx // the home page (which has access to the list of all blog posts)
|   β”œβ”€β”€ blog
|       β”œβ”€β”€ [blog].md // a template component that renders the blog posts under `/md/blog`
β”œβ”€β”€ md
|   β”œβ”€β”€ blog
|       β”œβ”€β”€ devii.md // this page!
        β”œβ”€β”€ whatever.md // every MD file in this directory becomes a blog post
β”œβ”€β”€ components
|   β”œβ”€β”€ Code.tsx
|   β”œβ”€β”€ Markdown.tsx
|   β”œβ”€β”€ <various others>
β”œβ”€β”€ loader.ts // contains utility functions for loading/parsing Markdown
β”œβ”€β”€ node_modules
β”œβ”€β”€ tsconfig.json
β”œβ”€β”€ package.json
β”œβ”€β”€ next.config.js
β”œβ”€β”€ next-env.d.ts
β”œβ”€β”€ .gitignore

TypeScript + React

Both React and TypeScript are baked into the DNA of Next.js, so you get these for free when you set up a Next.js project.

Gatsby, on the other hand, has a special plugin for TypeScript support, but it's not officially supported and seems to be low on their priority list. Also, after messing with it for an hour I couldn't get it to play nice with hot reload.

Markdown authoring

Using Next's special getStaticProps hook and glorious dynamic imports, it's trivial to import a Markdown file and pass its contents into your React components as a prop. This achieves the holy grail I was searching for: the ability to easily mix React and Markdown.

Frontmatter support

Every Markdown file can include a "frontmatter block" containing metadata. I implemented a simple utility function (loadPost) that loads a Markdown file, parses its contents, and returns a TypeScript object with the following signature:

type PostData = {
  path: string; // the relative URL to this page, can be used as an href
  content: string; // the body of the MD file
  title?: string;
  subtitle?: string;
  date?: number;
  author?: string;
  author_image?: string;
  tags?: string[];
  banner_image?: string;
  thumb_image?: string;
};

I implemented a separate function loadPosts that loads all the Markdown files under /md/blog and returns them as an array (PostData[]). I use loadPosts on this site's home page to render a list of all posts I've written.

Medium-inspired design

I used the wonderful react-markdown package to render Markdown as a React component. My Markdown rendered component (/components/Markdown.tsx) provides some default styles inspired by Medium's design. Just modify the style pros in Markdown.tsx to customize the design to your liking.

GitHub-style code blocks

You can easily drop code blocks into your blog posts using triple-backtick syntax. Specify the programming language with a "language tag", just like GitHub!

To achieve this I implemented a custom code renderer (/components/Code.tsx) for react-markdown that uses react-syntax-highlighter to handle the highlighting. So this:

```ts
// pretty neat huh?
const test = (arg: string) => {
  return arg.length > 5;
};
```

turns into this:

// pretty neat huh?
const test = (arg: string) => {
  return arg.length > 5;
};

RSS feed generation

An RSS feed is auto-generated from your blog post feed. This feed is generated using the rss module (for converting JSON to RSS format) and showdown for converting the markdown files to RSS-compatible HTML. The feed is generated during the build step and written as a static file to /rss.xml in your static assets folder. It's dead simple. That's the joy of being able to easily write custom build scripts on top of Next.js's getStaticProps hooks!

SEO

Every blog post page automatically populated meta tags based on the post metadata. This includes a title tag, meta tags, og: tags, Twitter metadata, and a link tag containing the canonical URL. You can modify/augment this in the PostMeta.ts component.

Static generation

You can generate a fully static version of your site using yarn build && yarn export. This step is entirely powered by Next.js. The static site is exported to the out directory.

After its generated, use your static file hosting service of choice (Firebase Hosting, Vercel, Netlify) to deploy your site.

Insanely customizable

There's nothing "under the hood" here. You can view and modify all the files that provide the functionality described above. Devii just provides a project scaffold, some Markdown-loading loading utilities (in loader.ts), and some sensible styling defaults (especially in Markdown.tsx).

To start customizing, modify index.tsx (the home page), BlogPost.tsx (the blog post template), and Markdown.tsx (the Markdown renderer).

Get started

Head to the GitHub repo to get started: https://github.com/colinhacks/devii. If you like this project, leave a ⭐️star⭐️ to help more people find Devii! 😎

To jump straight into the code, clone the repo and start the development server like so:

git clone [email protected]:colinhacks/devii.git mysite
cd mysite
yarn
yarn dev

I write essays like this one every month. Put your email in the box to get future proposals delivered to your inbox.

Subscribe