There is one fact that organises everything else: GitHub Pages does not run my code. No process of mine is alive on a server waiting for your request. The software that builds this site runs exactly once, in a throwaway container, and produces a pile of static files. After that, “serving” is just a global cache handing out pre-computed bytes.

That separation is the whole story. The computation and the distribution happen on completely different machines, owned by completely different fleets, at completely different times. This post traces the path of a single page - the one you’re reading - from a Markdown file in my private notes to bytes painted in your browser.


The build is a content heist, not a checkout

This site is generated by Quartz, a static-site generator that turns Markdown into HTML. The stock setup keeps your content in a content/ folder inside the Quartz repo itself. Mine doesn’t. The deploy workflow does something more deliberate:

- name: Clone vault
  run: |
    git clone --depth 1 --no-checkout \
      https://x-access-token:${{ secrets.VAULT_TOKEN }}@github.com/suisuss/.obsidian.git /tmp/vault
    cd /tmp/vault
    git sparse-checkout init --cone
    git sparse-checkout set "Writing" "Knowledge Base" "Files" "About Me" "index.md"
    git checkout

The Quartz repo is the machinery; the writing lives in a different, private repo - my Obsidian vault. At build time the CI runner clones the vault with a scoped token, sparse-checks out only a few directories, and copies them into Quartz’s content/.

Then the part that matters:

if head -c 9 "$f" 2>/dev/null | grep -q "GITCRYPT"; then
  continue
fi

The vault is encrypted at rest with git-crypt, and the runner never unlocks it. So every private note still carries its \0GITCRYPT magic bytes, and the copy step skips anything bearing that header. Encryption isn’t only protecting the repo - it’s the publish filter. A note reaches this website if and only if it’s plaintext in the vault. The default is secrecy; publication is the deliberate act of not encrypting a file.

That’s the right way round, and there is a human gate: I commit and merge every file into the vault myself, so nothing reaches the build that I didn’t put there. But the gate guards authorship, not classification. The decision that actually controls exposure - encrypt this, leave that plaintext - is one I make by hand, once, with no second reviewer. A file I forget to encrypt, or one that doesn’t match a crypt pattern, publishes on the next build exactly as cleanly as something I meant to share. The encryption rules fail open, so the real risk isn’t an unreviewed file - it’s a misjudged one.

After the copy, it’s ordinary Quartz: install dependencies, parse and transform the Markdown (wikilinks, math, syntax highlighting), and render everything to static HTML, CSS, and JS in a public/ directory. That directory is the entire product. Everything after this point is logistics.


The artifact is uploaded to a CDN, not deployed on a server

The build ends by packaging public/ as an artifact and handing it to GitHub’s Pages infrastructure, which ingests it and makes it the new content for suisuss.github.io/quartz. No file ever lands on a server I administer. There is nothing to SSH into.

The trigger block hides a subtlety:

on:
  push: { branches: [v4] }
  workflow_dispatch:
  schedule:
    - cron: "0 6 * * *"   # Rebuild daily to pick up vault changes

The Quartz repo rarely changes, but the vault changes constantly - and a vault edit doesn’t fire the Quartz repo’s workflow, because GitHub gives you no cross-repo event for it. The daily 06:00 UTC cron exists to paper over exactly that gap: once a day the pipeline re-clones the vault and rebuilds, so my writing surfaces within a day even when I never touch the site’s own repo. It’s a polling workaround for a dependency the platform won’t notify you about.


3. You open the URL: a name becomes four IP addresses

Now the distribution half. You open https://suisuss.github.io/quartz/. Before any connection, your browser needs an IP address, so it asks the Domain Name System. The answer, right now:

185.199.108.153
185.199.109.153
185.199.110.153
185.199.111.153

These are the four canonical GitHub Pages addresses, and the important property is that they are anycast. The same four IPs are announced from points of presence all over the planet. Your packets don’t travel to one datacentre - the internet’s routing fabric delivers them to the topologically nearest edge that advertises the route.

I can prove that from the response headers. My own request to this site was answered by a cache identifying itself as cache-mel11280-MEL - a Fastly node in Melbourne. A reader in Frankfurt hitting those identical four IPs would land on a German node instead. One address, many machines, geography resolved by routing rather than by a different number.


4. TCP, then TLS: building a trusted pipe

With an IP in hand, your browser opens a TCP connection to port 443 - the three-way handshake (SYN, SYN-ACK, ACK) that turns IP’s unreliable, possibly-out-of-order packets into an ordered, reliable byte stream. That is TCP/IP’s entire job: a dependable pipe built on an undependable network.

Immediately on top of it, TLS. Your browser and the edge negotiate an encrypted session - the server presents a certificate for *.github.io, keys are exchanged, and the two sides agree on which application protocol to speak. The response carries strict-transport-security: max-age=31556952, so after your first visit the browser refuses to even attempt unencrypted HTTP to this host for a year.

The negotiated protocol is HTTP/2 - the response status line reads HTTP/2 200. That matters here because a page like this pulls many small files (fonts, the site’s script bundle, prefetched pages). HTTP/2 multiplexes them all over the single TLS connection, instead of paying for a fresh handshake per file the way older HTTP effectively forced.


5. The request, and what’s actually on the far end

Only now does an HTTP request travel down the pipe: a verb and a path (GET /quartz/), a Host header, and the browser’s Accept, Accept-Encoding, and any cached validators. That’s all HTTP is - a request line, headers, an optional body, and a structured response. Everything below it exists to carry these few lines safely.

What answers is a layered system, and the response headers expose every layer:

  • via: 1.1 varnish and x-fastly-request-id - the first thing to touch the request is Fastly’s cache at the edge. Fastly sits in front of github.io and caches every successful response. For a static site this is almost the whole game: most reads never travel further than your nearest city.
  • x-cache: MISS - this particular object wasn’t in the edge cache yet (cold, or freshly purged after a deploy), so the edge had to fetch it from origin.
  • server: GitHub.com - the origin. This is GitHub’s own Pages serving tier, holding the public/ artifact the deploy job uploaded. It’s a fileserver fronted by a routing layer that maps the hostname to the right backend and streams the file. It runs no code of mine.

So the chain is: your browser → Fastly edge cache → GitHub Pages origin → the static file. On a cache hit it’s just the first two boxes. There is no database lookup for this content, no template rendered on demand, no application process. The expensive work happened once, in step 1, in a container that no longer exists.


6. Caching is the actual serving strategy

The freshness headers are where the design philosophy lives:

cache-control: max-age=600
etag: "6a12b809-92df"
last-modified: Sun, 24 May 2026 08:34:17 GMT

max-age=600 tells caches the page is good for ten minutes. The etag is a fingerprint of the bytes; on your next visit the browser sends it back and, if nothing changed, receives a tiny 304 Not Modified instead of re-downloading the whole page.

Combine this with the daily rebuild and you get the real propagation story. An edit to my notes becomes visible only after the cron fires, the build and deploy finish, the edge cache is purged, and your own browser’s ten-minute copy expires. Staleness is bounded by roughly a day, not by anything instant. That’s the price of never running a server: I trade write-latency for read-scalability. This site can absorb a front-page traffic spike without my noticing - but a typo fix isn’t live until the next rebuild.


7. Your browser reassembles a website, then the script takes over

The bytes arrive as gzipped text/html. Your browser parses the HTML, builds the DOM, discovers the linked stylesheet and script, fetches them over the same HTTP/2 connection, loads the fonts, and paints the page.

Then a second act, specific to this site having single-page navigation enabled. The script installs a client-side router. After the first full load, clicking an internal link does not trigger a fresh page navigation - the router fetches the target’s HTML in the background and morphs the current page into it, so it feels instant and the connection stays warm. Hovering a link prefetches its destination into a popover. The first hit is a classic server-fetched document; every hit after that is the page quietly pulling more static files from the same cache and re-rendering itself locally.

This is also why a single configuration value is load-bearing: the site lives at a subpath (/quartz/), not a domain root. That base is baked into every generated link, the sitemap, and the feed. Get it wrong and the router fetches paths off the root, every internal link breaks, and the navigation layer fails silently - even though the first page loaded fine.


The shape of the whole thing

Strip it to one sentence: my private vault is the source of truth, encryption decides what’s public, a CI container compiles the public subset to static files once, GitHub stores them, and a CDN hands out cached copies from a machine near you.

The two interesting decisions are both in step 1, not in the plumbing - pulling content from a separate encrypted repo so the writing and the renderer stay decoupled, and using the encryption itself as the publish gate so secrecy is the default and exposure is the deliberate act. The rest - DNS, TCP, TLS, HTTP, anycast, edge caching - is the same reliable machinery under every static site on the internet. The plumbing isn’t where this site is clever. The content boundary is.