The landing page for Bounda, an event sourcing and CQRS framework for TypeScript. Built with Astro and deployed to Cloudflare Pages.
Deployed as a Worker with static assets: wrangler.jsonc serves dist/, and 404.html answers
anything it does not find.
The documentation is a separate site: it lives in the framework repository, next to the code it documents, and is served at docs.bounda.dev. This repository holds only what answers "what is this and why", so anything about how belongs there.
pnpm install
pnpm dev # http://localhost:4321
pnpm build # dist/The 3D scene (src/scripts/landing/scene.ts) runs in a Web Worker on an OffscreenCanvas where the
browser supports it, and on the page otherwise; scene-client.ts decides and scene-worker.ts is the
worker. Either way the page paints its hero on the first frame and never blocks on three.js, which is
what keeps Lighthouse at 100 on mobile. pnpm build && pnpm lighthouse measures it the way CI does
(scripts/lighthouse.mjs: the mobile default and the desktop preset, median of three runs, reports in
lighthouse-reports/) and fails under the budget set there: 95 in performance, 100 in the rest. The
Cloudflare beacon is kept out of the audit, so the check works offline and never fails on a third party.
pnpm, like the framework repository, and not only for consistency: npm ci cannot install this
tree on Linux from a lockfile resolved on macOS, because rolldown ships its native bindings as
per-platform optional dependencies. The pnpm lockfile carries all of them.
Posts are Markdown files in src/content/blog/, one per post, named as their address:
src/content/blog/<slug>.md is served at /blog/<slug>/, with its social card at
/blog/<slug>/og.png. The front matter is typed in src/content.config.ts. A post with
draft: true shows on pnpm dev and is left out of the build, so it can be merged before it is
published. The blog also feeds /blog/rss.xml and /llms.txt.
Each post is translated into Spanish, Japanese and Brazilian Portuguese when it is published:
src/content/translations/<locale>/<slug>.md holds the title, the description and the body in
that language, and takes the date, the kind and the draft flag from the English post, which is
the original. It is served at /<locale>/blog/<slug>/, with its own card and hreflang links to
every version, x-default being the original (set in src/layouts/base.astro). The interface strings and the languages live in
src/i18n.ts. A translation whose original does not exist fails the build.
No page redirects by language: search engines pick the version through hreflang, and each link
that is shared points at its language. The original lists its translations in one line under the
post; a translation says where it comes from above it. Spanish is reviewed by the author and says
translated; Japanese and Portuguese are machine translations, say so, and link to a new issue to
report a mistake. GLOSSARY.md fixes how each language
renders Bounda's vocabulary, and how a machine translation is checked before it is published.
DESIGN.md is the design system both sites follow: palette, type, logo, the ledger
grammar for tables, motion. Its tokens live in src/styles/tokens.css, and the documentation
mirrors them on Starlight's variables in docs/src/styles/bounda.css of the framework repository.
A change to those tokens has to be carried across, or the two sites drift apart visually — which is
the reason this repository is separate from the framework but not independent of it.
A Worker with static assets, built by Cloudflare on every push to main:
| Build command | pnpm build — Cloudflare installs dependencies in its own step, from pnpm-lock.yaml |
| Deploy command | npx wrangler deploy |
| Root directory | / |
NODE_VERSION |
24, since Astro 7 requires >=22.12.0 |
PNPM_VERSION |
12.4.2, because allowBuilds in pnpm-workspace.yaml is pnpm 12 |
wrangler.jsonc serves dist/ and answers anything missing with 404.html. public/_headers sets the
response headers: a year's immutable cache for the hashed files under /_astro/, and the usual safety
headers for everything. The build image ships
pnpm 10, which reads pnpm-workspace.yaml as a workspace definition, so that file declares
packages and names the built dependencies under both allowBuilds (pnpm 12) and
onlyBuiltDependencies (pnpm 10).
If a push does not produce a build, check Settings → Builds: a Worker whose Git connection has lapsed keeps the repository listed while silently ignoring pushes.