This is now a Next.js Website

One of the things I did in between jobs is to convert this website from Pelican to become a Next.js app.

There are three main motivations:

  1. I want to be more familiar with "the web"
    Being a backend engineer means that I rarely touch frontend side of things. This'll give me the chance to make this site more modern and pretty.
  2. There's much hype around Next.js
    Colleagues and friends have talked a lot about Next. They demoed how easy it is to build a full-stack app in no time.
  3. My website is ancient
    I haven't updated the foundation of this site since I created it back in 2015. Let's be honest, using a Python library to generate website feels very old-school these days...

My Experience

I've really enjoyed using Next. The overall DX is very good. It makes it very easy to build a React app without in-depth knowledge about React. Although this website is all static currently, Next makes it possible for future extension with a mixture of static, sever-side rendered, and dynamically rendered on the client side.

Another cool thing is that it supports Typescript out of the box (with minimal configurations), so there's no need to transpile it back into (common) JS using external libraries. The module, routing and data fetching systems are all pretty intuitive.

I really recommend going through their tutorial. It's really good - it even contains basics of React if you're new to it like me. Based on the tutorial, I created this new version of the website in no time! I'm a fan.

The Fiddly Bits

With a major rewrite, there's always some quirks to iron out. Here are a few:

CSS

I finally did what I set out to do back in December 2020 โ€“ remove unused CSS. To a certain degree, the CSS files are now more lightweight and precise, and I've a (slightly) better idea of what's going on. Well, CSS will always remain a myth to me, so any improvement is good news.

โ†’ The Problem

Somehow, my old Pelican-generated site was able to handle :hover for both both touch and with a mouse without using @media queries. (I suspect it's because pages are treated as "separate" rather than connected as an "app"? Don't quote me on this! ๐Ÿ˜…)

Sure, I can disable "hover" effects (e.g. when you hover over links, they turn orange) using @media queries. After a lot of digging, a Stackoverflow answer gave me a hint!

Really, the desired effects would be:

  • With a mouse, I want these effects to happen when you hover over links.
  • With a touchscreen, I want these effects to happen when you tap on them.

โ†’ The Solution

So, what I ended up doing is to use a combination of :active for touch devices, and @media queries to only enable :hover effects on a device with a mouse / pointing device. It looks like this:

/*
Cater for touchscreen: change colours when tapping a link
*/
a:active {
  text-decoration: none;
  color: var(--a-hover-color);
}

/*
Cater for mouse: change colours when hovering on a link
*/
@media (hover: hover) and (pointer: fine) {
  a:hover {
    text-decoration: none;
    color: var(--a-hover-color);
  }
}

Remark, Rehype โ€“ RE: hell

โ†’ The Problem

Pelican handles markdown and rst files out of the box. Sadly, not for Next.js.

โ†’ The Solution

Luckily (well after a lot of fiddling), a combination of Remark and Rehype plugins did the job.

Remark and Rehype are both in the unifiedjs collection. To be honest, I'm not too sure about their differences. Anyhow, plugins from the two gave me the desired effects.

Rehype is needed for pretty code blocks which I'll explain in the next section.

Using unifiedjs, and Remark and Rehype plugins look like this:

const processedContent = await unified()  // use the unifiedjs processor
  .use(remarkParse)  // parse as remark
  .use(remarkGfm)    // use the remark-gfm plugin for GitHub flavoured markdown
  .use(remarkGemoji)  // use GitHub short code to reference emojis
  .use(remarkImages)  // makes image syntax simpler in resultant HTML
  .use(remarkRehype)  // convert to rehype
  .use(rehypePrettyCode, { theme: "dracula" })  // so that we can make code blocks pretty
  .use(rehypeStringify)  // convert syntax tree into string
  .process(matterResult.content);  // the markdown input
const contentHtml = String(processedContent);  // get a string output for later use

Code Blcok

โ†’ The Problem

Pelican uses python-markdown which supports a bunch of extensions. In the old site, I used CodeHilite. The library also supports adding line numbers to each line which is fantastic.

In the Next.js world, this becomes a bit more challenging.

Initially, I used rehype-highlight which uses highlightjs underneath. All was well, until I wanted line numbers... It's not an oversight but a feature, apparently.

We'll have to agree to disagree there.

โ†’ The Solution

On second try, I found rehype-pretty-code which works superbly, as you've seen above. It's quite simple to set up, and integration with shiki means it's easy to theme code blocks.

Hosting as an S3 Static Site with Cloudfront

This took me a while to figure out...

โ†’ The Problem

Next.js routing convention means that it's not directly maps to an S3 resource which uses the actual file name. This creates the confusion that subpages can be accessed when clicking through from melaus.xyz/, but not going directly to a subpage, e.g. melaus.xyz/about.

โ†’ The Solution

My solution is to strip out .html from all html file names, and set its Content-Type to text/html in S3. The badsyntax/github-action-aws-s3 action makes this easy.

However, there's a caveat โ€“ pages referenced in AWS settings seems to require the .html suffix for things to work, regardless of Content-Type settings. This includes the default page (index.html) and error page (404.html).

Theming

If you recall, I added dark theme functionality back in April 2020 (joys of lockdown).

I made some changes to fit it in a React environment, utilising React's useEffect(). I loosely based it on this Medium article, but using simple CSS instead of Tailwind.

Summary

Apart from these quirks, the move to Next.js was pretty painless. This site is now set for future expansions and updates.