Building this blog (part 1)

in the beginning...

Wed, 24 Apr 2024

Overview

This post is primarily to remind me how this is built, but maybe someone else will find it helpful.

Tech stack:

  • Astro JS
  • Daisy UI
  • Ramda

Astro is doing most of the heavy lifting as a component-oriented static site generator. Within Astro, I’m using Ramda for functional data processing. Most of the presentation layer code is Daisy UI, which is a Tailwind-based pure CSS component library. Client-side scripting is negligible: using Alpine for saving/loading dark mode state.

These posts are managed as markdown files on local disk via an Astro content collection. They start life as my development notes and may occasionally get published as rough cuts. When that happens, I’ll come back to expand them into grammatical sentences, while trying to preserve the content.

Images are generated using Stable Diffusion variants. The prompts should appear as tooltips, if you’re curious. The content is written by my awkward self, though I’m not above having the LLM du jour (currently Claude) do a little proof-reading. Any AI generated text will be noted as such, usually in the context of working with language models (e.g. prompt engineering, fine-tuning, etc) or just poking fun at GenAI fails.

Project setup

Nix

Let’s start with a very basic nix shell:

# shell.nix
with import <nixpkgs> {};
mkShell {
  PROJECT_DIR = toString ./.;
  packages = [
    yarn
    nodejs_21
    nodePackages.wrangler
  ];
}

You can use direnv to automatically activate this environment when working in this directory.

# .envrc
source_up_if_exists
layout node
use nix

I suggest placing .envrc into your .gitignore when working in a shared repository. There is the possibility of running arbitrary shell commands on a pull otherwise.

yarn create

With that ready, let’s initialize the project. You can initialize the project to target your preferred deployment target (Vercel, Netlify, roll-your-own VPS, etc), but I’m going with Cloudflare for this round.

$ yarn create cloudflare astroflare -- --framework=astro

Don’t include sample files, just select the “Empty” template.

Strict Typescript will be needed for Astro content collections.

You can let it deploy the application, if wrangler is already setup, but since this is an empty project, there’s not much to see.

Start a local dev server with yarn dev

patchelf

… but the dev server doesn’t quite work right.

Error: spawn /code/webdev/astro-course/astroflare/node_modules/@cloudflare/workerd-linux-64/bin/workerd ENOENT
    at ChildProcess._handle.onexit (node:internal/child_process:284:19)
    at onErrorNT (node:internal/child_process:477:16)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21)
Emitted 'error' event on ChildProcess instance at:
    at ChildProcess._handle.onexit (node:internal/child_process:290:12)
    at onErrorNT (node:internal/child_process:477:16)
  ...

A little digging 1 reveals that Cloudflare’s workerd is trying to load shared libraries from /lib64 which isn’t valid under NixOS. We can fix this with patchelf. However, any time a new package is installed, workerd gets overwritten. We can have yarn run patchelf after every install by adding a postInstall hook in package.json.

  "scripts": {
    "postinstall": "command -v patchelf && patchelf --set-interpreter
        $(ldd node_modules/@cloudflare/workerd-linux-64/bin/workerd |
          grep -oh '/nix/store\\S*ld-linux\\S*')
        node_modules/@cloudflare/workerd-linux-64/bin/workerd"
  },

This will only run on systems with the patchelf command… so just NixOS. We could wrap the command with writeShellScript in shell.nix to make it a little cleaner, but this gets the job done.

Dependencies

Let’s add the dependencies we’ll use later.

$ yarn astro add @astrojs/tailwind
$ yarn astro add @astrojs/alpinejs
$ yarn add daisyui ramda @tailwindcss/typography
$ yarn add --dev @types/ramda

Unlike tailwind and alpinejs, @tailwindcss/typography is a plain NPM package instead of an Astro plugin. You’ll need to configure it as a tailwind plugin in tailwind.config.mjs. According to the Daisy UI documentation, order matters:

	plugins: [
		require('@tailwindcss/typography'),
		require("daisyui"),
	],

Now we’re ready to start coding. Read the next part.

Footnotes

  1. https://github.com/cloudflare/workerd/discussions/1515