asciicrawler.com

· Astro · TypeScript · nginx · Docker · WebSockets · AWS ECR

This site is the project. It is built with Astro in static output mode — every route is rendered to HTML at build time, then served by nginx out of a container image. There is no application server in front of it, which keeps the whole thing to a handful of files on disk.

The chat panel

The interesting part is not the static half. The page opens three websockets against a separate chat backend: one for messages, one for shared cursor positions, and one for live server stats. Every endpoint is namespaced per room, so the panel on the homepage is just one room among however many the backend is holding.

Reconnection is handled by a small socket wrapper with exponential backoff and jitter — the delay floor, ceiling, connect timeout, and jitter band are all tunable through typed environment variables that the build validates before it will produce output. A malformed value fails the build rather than shipping a page that silently cannot connect.

The interface

The CRT treatment is layered: a looping webm behind the content, animated scanlines, and a noise overlay. All three collapse to a still poster under prefers-reduced-motion, and the poster itself is only fetched when that query matches, so the default path never downloads an image it will not paint.

Shipping it

A multi-stage Dockerfile builds the site and copies dist/ into an unprivileged nginx image. GitHub Actions builds that image on every push to master and pushes it to Amazon ECR under both a production tag and the commit SHA.

Two details worth keeping: the build gzips its own output ahead of time so nginx can serve the compressed bytes with gzip_static instead of recompressing on every uncached request, and the Content Security Policy is generated at build time from hashes of the actual inline scripts and styles, so tightening the policy never means hand-maintaining a hash list.

All projects · Homepage