All posts

· 2 min read

New site: Hugo, Cloudflare Pages and zero JavaScript

This site has just been rebuilt from scratch. The previous version was a React app that fetched projects and posts from APIs in the browser; it worked, but the content only existed after JavaScript ran. For a portfolio and a blog that’s backwards: the content is the product, and it should arrive ready.

The new version is a static site generated with Hugo and deployed on Cloudflare Pages. Here’s a summary of the decisions.

Why Hugo

  • It’s a single binary. No node_modules, no bundler step. The whole site builds in under a second.
  • Content is Markdown. Each post and each project is a folder with an index.md and its images next to it.
  • Built-in image processing. Project screenshots go into the repository as PNG and come out as WebP, in several sizes, with srcset.
  • Truly multilingual. Portuguese and English share the same templates, and Hugo generates hreflang tags and one sitemap per language.

Custom theme, no dependencies

Instead of adopting an existing theme, I wrote the templates and a single CSS file. The result weighs a few kilobytes and loads no JavaScript at all.

Light and dark mode follow the system preference using only CSS:

:root {
  color-scheme: light dark;
  --bg: #f5f2ec;
  --text: #1a1814;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #121110;
    --text: #ece7dd;
  }
}

No toggle, no localStorage and no flash of the wrong theme on load.

SEO from the first commit

A few things every page ships with:

  • unique title, description and canonical URL;
  • Open Graph and Twitter Cards, with a share image generated at build time for each page;
  • JSON-LD structured data (Person, WebSite, BlogPosting, BreadcrumbList);
  • sitemap.xml, robots.txt and an RSS feed;
  • self-hosted fonts with preload and font-display: swap.

Deploy

The GitHub repository is connected to Cloudflare Pages: every git push to main triggers a new deploy, and every pull request gets a preview URL. The build configuration in the dashboard comes down to three fields:

Build command:     hugo --gc --minify
Output directory:  public
HUGO_VERSION:      0.167.0

Pinning the version with HUGO_VERSION matters: without it, Pages uses the build image’s default version, which tends to lag well behind the current one.

What’s next

The plan is to use this space to write down what I learn day to day with React Native, Expo, Cloudflare Workers and product development. If you want to follow along, subscribe to the RSS feed.

Contact

Have a project in mind?

Tell me what you need and I'll get back to you with next steps and an estimate.