How We Build and Ship the WebHost24 Site Each Week
The tools we use to build the site: Astro 7, Svelte 5, and Tailwind 4. We also make our own share image for each page. We keep each page small, and we ship new pages each week.
The R221 site runs on this stack. It is the one you read now. This post is the one true record. It shows how the site is built.
Frontend.
- Astro 7 is the web framework. It runs in server-side rendering
(SSR) mode. The mode is
output: "server". It runs on the@astrojs/nodeadapter. Each reply is fresh. No old HTML is kept. The same Docker build runs on any host. - Svelte 5 runs the few parts you can click. The mobile menu is one. The cookie banner is one. The sticky header is one. So is the billing toggle. Most parts are plain HTML by default. Svelte is opt-in for each one.
- Tailwind 4 comes in through the Vite plugin, the new way. The
brand colours are few by choice. There are just four of them. The
first is primary
#002071. Next is natural-dark#1F1F1F. Then it is natural-light#F1F1F1. The last one is secondary#FFFF00. All of them sit in one place inglobals.css. That place is the@themeblock. We add no tints or shades outside the locked list, per AGENTS.md section 6. - bits-ui gives the few plain parts that must work for all readers. The accordion is one. The sheet and dialog are two more. So are the switch, the tabs, the tooltip, and the select. It stands in for the R220 Radix UI setup.
Per-app OG images.
A custom Node + sharp script builds one 1200x630 PNG file. It does this
for each app MDX file. The script is scripts/build-app-og-pngs.mjs. It
runs at prebuild time. Each PNG puts the app brand colour in the
back. It puts the app title on top. It puts a 60-char wrap of the
excerpt there too. The script sits in the prebuild chain. So the PNGs
are made fresh before each astro build.
Build pipeline.
The npm run build runs three steps in a row.
prebuildrunsbuild-og-png.mjs,build-llms-txt.mjs, andbuild-app-og-pngs.mjs.buildrunsastro build. It builds the static HTML. It builds the JS chunks for the islands too.startruns the dev server. It is not part of CI. It is just for localnpm run dev.
You get a static dist/client/ folder. Run du -sh dist/client in the
repo to see the size now. There is also an entry point at
dist/server/entry.mjs. It runs on port 2828 in standalone mode.
Deployment.
The Docker image has one stage. It uses alpine. It is built from the
repo Dockerfile. The build step does npm ci. Then it runs
npm run build. The run step puts dist/server/entry.mjs in the image.
It puts dist/client/ in too. Then it runs
node ./dist/server/entry.mjs.
The same build goes to Palitana. It lands on a Kubernetes cluster. Our
ops team runs it. Health checks hit /healthz. It returns
{status: "ok"}. The middleware sets Cache-Control headers. /fonts/
gets s-maxage 7d. /apps/ and /brand/ get s-maxage 1d.
Why this stack.
Astro ships 0 JS out of the box. Just the islands add code. Our biggest island bundle is the cookie banner, ~15 KB gzipped. The JS payload on the public site is ~30 KB gzipped. That sits well under the 50 KB per page budget from spec section 9.
The $state and $derived code in Svelte 5 is small. Each island ships
in a few KB gzipped. Run npm run build && du -h dist/client/_astro/*.js
in the repo to see the size of each one now.