TLDR: The base website is hand-written and uses Bootstrap. The blog is built with Hugo and uses the excellent PaperMod theme (with a single, minor addition). Hosting is via Cloudflare Pages.

The website

One of the primary design goals was that Madhu should be able to edit the blog and make textual edits to the website directly from GitHub. This ruled out fancy React-based SSGs such as Next or Gatsby. I eventually settled on Bootstrap and Hugo, picking Hugo over Jekyll simply because I liked how PaperMod looked.

Madhu had made some Figma designs for me, so the initial prototype was fairly straightforward.

Screenshot of figma

The website is structured like this:

./
├── 404.html
├── blog-raw/
│   ├── archetypes/
│   ├── config.yml
│   ├── content/
│   ├── public/
│   └── themes/
├── css/
│   └── base.scss
├── favicon.png
├── img/
│   ├── IMG_20221007_142632622_HDR.jpg
│   ├── ImpactMojo/
│   ├── headshot.png
│   ├── kidzee preschool/
│   └── overwhelmed by work/
├── impactmojo.html
├── index.html
├── kidzee.html
├── overwhelmed-by-work.html
├── package-lock.json
├── package.json
└── work.html

Optimizations

I decided to optimize the website as a form of procrastination. I chose Parcel as a bundler, since it’s basically zero-config. I briefly considered using Webpack, but it seemed impossible to configure for a static website. Webpack seems to always want a JS entry point, and I don’t even have a single line of JS on the entire website!

Anyway, Parcel is great and easy. It does:

  1. minification
  2. content hashing
  3. sass processing
  4. image resizing, conversion and compression

The image stuff is especially cool. This is how I references images in my markup:

<img
  class="headshot my-3 mx-auto"
  alt="a portrait of madhushree"
  src="img/headshot.png?as=webp&width=600&quality=50"
/>

Parcel can take Madhu’s portrait (500 kB!) and process it into a 50 kB version, with basically zero effort or configuration from me. It even works out of the box on Cloudflare’s build environment.

Reducing CSS bundle size

On the topic of CSS, this is also the first time I actually compiled Bootstrap’s component files into my project instead of linking to the mega Bootstrap build. That’s really the only reason I used Sass at all, coincidentally.

@import "../node_modules/bootstrap/scss/functions";
@import "../node_modules/bootstrap/scss/variables";
@import "../node_modules/bootstrap/scss/maps";
@import "../node_modules/bootstrap/scss/mixins";
@import "../node_modules/bootstrap/scss/utilities";
@import "../node_modules/bootstrap/scss/root";
@import "../node_modules/bootstrap/scss/reboot";
@import "../node_modules/bootstrap/scss/type";
@import "../node_modules/bootstrap/scss/images";
@import "../node_modules/bootstrap/scss/containers";
@import "../node_modules/bootstrap/scss/grid";
@import "../node_modules/bootstrap/scss/helpers";
@import "../node_modules/bootstrap/scss/utilities/api";

body,
html {
  font-family: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
    "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
    "Segoe UI Symbol", "Noto Color Emoji";
}

I think this reduced the size of the CSS bundle from 300+ kB to about 100 or so.

For removing unused css, I used purgecss after the Sass compile step. I couldn’t figure out how to add it as a plugin to Parcel (conflicts related to Sass and plugin order I think) so I had to add it separately to my build.

With PurgeCSS, my bundle size came down to around 7 kB (~ 400 LoC of CSS) which is where I’ve settled at for now.

Each webpage has around 10 or so CSS rules in an inline stylesheet in addition to the base CSS bundle.

The blog

One of the interesting things about the blog is that its output directory is configured to be outside the blog base directory. I’m surprised this works – it seems like a footgun waiting to go off.

publishDir: "../blog"

I made a small patch to the PaperMod theme so that it supports lastmod entries in Hugo frontmatter. You can look at my fork for details. a blog post showing a last updated time

Cloudflare Pages

Let’s look at the build incantation that’s on Cloudflare.

npm run build && cd dist && mkdir min && \
npx purgecss --css *.css --content *.html -o min && \
mv min/*.css . && rm -rf min && cd .. && \
cd blog-raw && hugo && cd .. && mv blog dist

Fairly straightforward, except for the PurgeCSS and Hugo directory shenanigans.

Figuring out how to serve the favicon was interesting. I only had a png, and I didn’t want to go around adding link tags on every webpage. Using an .ico would have meant no more markup but Parcel didn’t have support for processing .ico and that would have meant another step in the build pipeline. Adding a Cloudflare 301 redirect for favicon.ico to the png was a nice hack for this. a page rule showing a permanent redirect from favico.ico to favicon.png

www -> apex redirect

Contrary to what Cloudflare docs suggest, it seems Bulk Redirects don’t work for this. A Page Rule did the trick. Don’t forget to add a rel=canonical tag for SEO.

a page rule showing a permanent redirect from www.madhushreek.com to madhushreek.com

Random Thoughts

  • I’m surprised that neither Hugo or Jekyll have a straightforward way for me to write raw HTML pages. They seem to insist on processing every single page.

Todo

  • Support comments via utterances or similar
  • Figure out how to do sitemaps
  • Better SEO tags