Most explanations of how websites work start in the middle. They assume you already know what a server is, or what HTML means, or why anyone would write a configuration file. This one starts at the beginning.
what a website actually is
When you visit a website, your browser (Chrome, Firefox, Safari, whatever) sends a request across the internet to a computer somewhere asking for a file. That computer (called a server) sends back a text file. Your browser reads that file and draws what you see on screen.
That text file is HTML. It looks like this:
<h1>Hello</h1>
<p>This is a paragraph.</p>
The browser reads the tags, <h1> and <p>, and knows to make one thing: a big heading and the other body text. A stylesheet (CSS) tells it what color and size to use. A script (JavaScript) can make things move or respond to clicks. These three file types, HTML, CSS, and JavaScript, are everything a browser can understand. Every website, no matter how complex it looks, is ultimately delivered as some combination of those three.
The simplest possible website is a single HTML file sitting on a computer that’s connected to the internet. Paste it there. Done. Anyone with the address can ask for it and get it back.
the two kinds of websites
There’s a divide in how websites are made. On one side: static sites, where the files are prepared in advance and handed out exactly as stored. On the other: dynamic sites, where a program runs on the server every time someone visits, assembles a fresh page from a database, and sends it back.
Static is fast and simple. Dynamic is flexible but heavier: there is a database, a running program, and moving parts to maintain.
This site is mostly static. Every page is a pre-built HTML file. When you visit /shelf, the server finds shelf/index.html and hands it to your browser. Nothing runs on my end. The file was already there.
the problem static sites used to have
Writing HTML by hand gets tedious. Every page needs the same <head> block, the same navigation, the same font imports. If you want to change the footer, you change it in fifty files. If you want to add a new page, you copy-paste a skeleton and fill it in.
The solution is a static site generator, a program you run on your computer that takes templates and content and assembles them into final HTML files. You write the shared parts once. The generator stamps them out across every page.
This site uses Astro. It’s a static site generator that lets you write templates in .astro files, a mix of HTML, CSS, and a small block of logic at the top called the frontmatter. You run npm run build and it produces a dist/ folder full of plain HTML, CSS, and JavaScript files, ready to be put on a server.
how astro turns files into pages
Astro follows a simple rule: every file inside src/pages/ becomes a URL.
src/pages/index.astro → /
src/pages/work/index.astro → /work
src/pages/shelf/index.astro → /shelf
Each of those .astro files contains a template. At the top, between the --- fences, is the frontmatter: JavaScript that runs at build time to fetch data, import other files, read keyboard events, or do math. Below that is the HTML template. Astro evaluates the frontmatter, plugs the results into the template, and writes out the finished HTML.
layouts: the shared wrapper
Almost every page on this site looks the same structurally: there is a <head> with fonts and metadata, a <body>, and consistent padding. Rather than repeat that in every file, there’s a layout: a reusable wrapper that pages slot their content into.
Base.astro — the HTML shell. <head>, fonts, theme script.
└── Page.astro — adds the [b]ack link, page title, and section icons.
└── shelf/index.astro — the actual shelf content.
Page.astro accepts a title prop and a slot. Any page that uses it passes its own content into the slot; Page.astro wraps it in the consistent frame. It also accepts a section prop; when present, it looks up the icons for that section and renders them in the top-right corner of the page.
There’s a second layout, Post.astro, used for reading-focused drivel posts. It adds a date line below the title and constrains the content width for comfortable line length.
the theme: one color file for everything
Before the first frame of any page appears, a small inline script runs. It checks browser storage for a saved color palette of ten values: background, foreground, muted text, accent, and so on. It writes those values as CSS custom properties on the page root. Every color in every style references those variables.
Where do the colors come from? I use a desktop configuration tool called Noctalia that manages my system’s visual theme. When I pick a wallpaper, Noctalia figures out which color scheme goes with it, fills in a template I wrote, and saves the result to a file called theme.json in the site’s public/ folder, which gets served as-is without any processing. The site fetches that file at runtime, reads the ten values, and stores them. Next visit, the stored values apply instantly.
This means the site’s colors reflect my actual desktop at the time of the last wallpaper change. It’s described in more detail in the color scheme post.
data files: just arrays
The shelf, work, and icon pages are driven by plain TypeScript arrays in src/data/. These are represented as simple lists; no API or database calls needed.
// src/data/shelf.ts
export const stories = [
{ title: 'Men Without Women', author: 'Haruki Murakami', year: 2017, isbn: '9780451494627' },
];
shelf/index.astro imports that array and loops over it to build the list and cover strip. Adding a book means adding one line to the array. Same for work entries in src/data/work.ts.
The hand-drawn icons that appear in each section’s header live in src/data/icons.ts, a single source of truth shared between the section pages and the SVG explorer post. Each icon carries its id, the section it belongs to, a short label, a tooltip, and the raw SVG path data.
The book covers come from Open Library, a public book database. The shelf page constructs a URL from each ISBN and sets it as the image source. The browser fetches the cover directly from Open Library’s servers at load time.
The film section works differently. At build time, when npm run build runs, the shelf page fetches the RSS feed from my Letterboxd profile. Letterboxd publishes a public XML feed of every film I log. The build parses it, extracts titles, years, ratings, and poster image URLs, and bakes the result into the static HTML. The film list is a snapshot from whenever the site was last built.
drivel: content as files
For longer writing, this site uses Astro’s content collections. The idea: put markdown files in a folder; get pages automatically.
src/content/drivel/
00-color-scheme.md
01-site-arch.md ← this post
A configuration file (src/content.config.ts) tells Astro to treat that folder as a collection called drivel. Each .md file has a frontmatter block at the top with its title, date, and description. Astro reads the whole collection at build time.
---
title: How This Site Works
date: '2026-05'
description: A ground-up explanation...
---
Content starts here.
The index at /drivel reads the collection and renders the list, sorted by file number so newer posts (higher numbers) appear first. Each post gets a URL derived from its filename minus the number prefix: 01-site-arch.md becomes /drivel/site-arch.
The dynamic route src/pages/drivel/[slug].astro does the work. At build time, Astro calls its getStaticPaths() function, which returns one entry per collection post. Astro generates a static HTML file for each one, rendered through Post.astro.
Posts that need interactive elements, like the color scheme page, get a companion .astro file at the same path. That static file takes priority over the dynamic route, and renders the markdown prose alongside whatever custom components it needs.
what runs in the browser
Nearly nothing. The design goal is that the page works as plain HTML. The JavaScript that exists serves specific purposes:
- The theme script in the base layout applies colors before the first paint, preventing a flash of wrong colors.
- The homepage has a canvas-based animation, a clock showing local time at UIUC, and a subtitle that cycles through descriptions.
- The shelf page syncs the horizontal cover strip’s scroll position to the text list’s scroll position, and cycles a quote panel through a shuffled pool on click or keypress.
- The color scheme page fetches live theme data and runs the preset browser.
- Every interior page listens for the
bkey to navigate back.
Where there’s a CSS equivalent, JavaScript is skipped entirely. The SVG icon explorer on the SVGs post selects and highlights icons with no script: just radio inputs and the CSS :has() selector.
None of it uses a framework. No React, no Vue, no build-time component hydration. Everything is standard browser APIs: document.querySelector, fetch, requestAnimationFrame, and addEventListener.
adding a new page
To add a new static page: create src/pages/name/index.astro, import Page.astro, write HTML in the slot.
To add a new drivel post: create src/content/drivel/NN-post-name.md with frontmatter. Nothing else. The collection picks it up, the index lists it, the route exists. If it needs interactive elements, also create src/pages/drivel/post-name.astro.
To add a book or film: edit src/data/shelf.ts. To add a work entry: edit src/data/work.ts. Rebuild.
Nothing is hidden in a framework. If something looks wrong, you can find it.