Recently, I migrated the unwieldy of crate2nix to a much more structured GitHub page. After some research, I settled on Astro with the Starlight Theme since I had head good things about Astro at the NixCon 2023 in Darmstadt (happy times!).

This is an especially appealing choice if you want to

  • start easy with mostly simple documentation in *.md files, but
  • potentially later extend it with some client-side logic with React.

But on we go! Let's get our GitHub Page up and running.

What you need to get started

This is a follow up on Tutorial: Using flake-parts to set up a nodejs devshell. We assume that you already have set up

  • a flake-based project using, and
  • setup a devshell with the right version of npm.

Creating the docs directory

Now that we have npm at hand, we can follow the instructions of Starlight on how to get started and answer the prompts as they come:

❯ npm create astro@latest -- --template starlight

 astro   Launch sequence initiated.

   dir   Where should we create your new project?
      ◼  tmpl Using starlight as project template

  deps   Install dependencies?

    ts   Do you plan to write TypeScript?

   use   How strict should TypeScript be?

   git   Initialize a new git repository?
      ◼  Sounds good! You can always run git init manually.

      ✔  Project initialized!
         ■ Template copied
         ■ Dependencies installed
         ■ TypeScript customized

  next   Liftoff confirmed. Explore your project!

         Enter your project directory using cd ./docs
         Run npm run dev to start the dev server. CTRL+C to stop.
         Add frameworks like react or tailwind using astro add.

         Stuck? Join us at

╭──🎁─╮  Houston:
│ ◠ ◡ ◠  Good luck out there, astronaut! 🚀

Local development

For local development, you can use a fast turn-around npm-based workflow:

❯ npm run dev

> docs@0.0.1 dev
> astro dev

 astro  v4.1.1 ready in 590 ms

┃ Local    http://localhost:4321/
┃ Network  use --host to expose

23:13:21 [WARN] [content] The i18n collection is defined but no content/i18n folder exists in the content directory. Create a new folder for the collection, or check your content configuration file for typos.
23:13:21 watching for file changes...

The exposed web server will update as you edit or display any potential errors.

The npm and node are supplied by nix and all collaborators with nix and direnv will seamlessly have the same version available.

But what about reproducible production builds?

Hermetic build with nix

Let's say we are satisfied with our edits and want a reproducible build of our docs. Copy the following as flake-module.nix into your docs folder:

# docs/flake-module.nix
perSystem = { config, self', inputs', pkgs, lib, system, ... }: { = pkgs.buildNpmPackage {
      pname = "docs";
      version = "0.1.0";

      inherit (config.packages) nodejs;

      src = ./.;

      buildInputs = [

      nativeBuildInputs = [

      installPhase = ''
        runHook preInstall
        cp -pr --reflink=auto dist $out/
        runHook postInstall

      npmDepsHash = "";

You remember our drill from the last article? You have to add the new ./docs/flake-module.nix to the imports in your flake.nix and ensure that all relevant files have been added to the git index with git add.

You can now try to build your docs like this:

nix build -L .#docs

Did you notice the empty string for the npmDepsHash before? Well, now nix will complain about this while also telling us the expected value:

warning: found empty hash, assuming 'sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='
error: hash mismatch in fixed-output derivation '/nix/store/mpnydavlxdww9fv2n2aic5mwnapnch7f-docs-0.1.0-npm-deps.drv':
            got:    sha256-iyz7+GeVYNDa0cLlz8PGmTNAQVetnt87ndfP0vUjxLw=
error: 1 dependencies of derivation '/nix/store/dcrayn9gy58hk0raq53ssca3h2js3alp-docs-0.1.0.drv' failed to build

We'll update npmDepsHash accordingly in our flake-module.nix:

  # ...
      npmDepsHash = "sha256-iyz7+GeVYNDa0cLlz8PGmTNAQVetnt87ndfP0vUjxLw=";
  # ...

NOTE: Your hash value will be different if any of the dependencies have a new version in the starlight template! Use the hash that nix has output behind "got."

Now nix build .#docs should build your docs, store them in the /nix/store and provide a symlink called result to them:

❯ ls -al result/
total 72
dr-xr-xr-x      9 root  wheel       288 Jan  1  1970 .
drwxrwxr-t@ 65535 root  nixbld  5130176 Jan 11 23:33 ..
-r--r--r--      1 root  wheel      8474 Jan  1  1970 404.html
dr-xr-xr-x     10 root  wheel       320 Jan  1  1970 _astro
-r--r--r--      1 root  wheel       696 Jan  1  1970 favicon.svg
dr-xr-xr-x      3 root  wheel        96 Jan  1  1970 guides
-r--r--r--      1 root  wheel     17758 Jan  1  1970 index.html
dr-xr-xr-x     14 root  wheel       448 Jan  1  1970 pagefind
dr-xr-xr-x      3 root  wheel        96 Jan  1  1970 reference

Woooo! Our docs!


Well, when committing our changes, we'll see the "result" symlink.

We should add it to .gitignore:

echo "result*" >>.gitignore

The star makes it also cover builds with multiple results.

Why didn't we create a .gitignore for the npm_modules etc? It was already done for us by the starlight template.


It was easy to build our Starlight page with nix. But this is mostly useful, if we also deploy it somewhere!

Let's do that in the next episode of this tutorial.

Feel free to give me feedback/ask questions at discourse or in a GitHub issue. I want to hear your thoughts: so feel free to err on the side of commenting too much.