Selected work
Bemo Buying Guide Platform
A content platform for a product-review and buying-guide site, built on Astro content collections with a validated MDX schema, generated structured data, and a static build deployed behind Docker and Traefik.

What It Is
Bemo is a content platform for long-form articles, built on Astro content collections instead of a traditional CMS. Every article is an MDX document validated against a schema at build time, which is what drives the listing pages, feeds, and structured data described below.
Content Model
Articles load through Astro’s content layer with a glob loader over a directory of MDX files, validated against a Zod schema at build time: category, tags, and publish/updated dates are all typed frontmatter, not free text. Reading time isn’t a field anyone fills in either, it’s computed from the article body’s word count at build time. Listing pages, breadcrumbs, and per-article structured data are all generated from that same schema, so a listing page and an article’s JSON-LD can’t drift out of sync the way they would if both were maintained by hand.
Not everything needs to go through that machinery, though. Static pages like the about page are plain Astro pages with hand-written markup rather than collection entries, since they don’t need to appear in a listing, a feed, or search the way an article does. Routing content through a schema it doesn’t need would just be overhead.
Built for Both Readers and Crawlers
Each article publishes its own BlogPosting-type JSON-LD linked back to a single Organization entity declared once for the whole site, rather than every page repeating the same entity data. Alongside that, the build generates an RSS feed, a JSON feed, and a sitemap directly from the content collection, so none of those outputs can go stale relative to what’s actually published.
The same content collection also drives an llms.txt file, a plain-text index of the site declared as an alternate representation in the page head. That gives LLM-based crawlers and browsing agents a direct answer to what the site is and what’s on it, instead of forcing them to parse rendered HTML the way a traditional search crawler would.
The interface is Tailwind CSS on top of Astro’s component model. Category filtering and on-page search run against the same collection data at build time, so there’s no separate search index to keep in sync.
Performance and Accessibility
Bemo ships as a static Astro build with no server-side rendering and no client-side framework runtime, which shows up directly in the numbers. A Lighthouse run against the production site:
| Performance | Accessibility | Best Practices | SEO | |
|---|---|---|---|---|
| Mobile | 100 | 100 | 100 | 100 |
| Desktop | 100 | 100 | 100 | 100 |
Largest Contentful Paint comes in at 0.8s on desktop and 2.3s on mobile, with 0ms Total Blocking Time and zero Cumulative Layout Shift on both, at a page weight around 230–260 KiB. None of that is accidental: semantic landmarks, correct ARIA usage, alt text on every image, and a well-formed accessibility tree are build-time requirements, not a pass applied after the fact, which is also what lets the same markup serve screen readers and AI browsing agents without extra work.
Deployment
It’s a multi-stage Docker build: one stage installs dependencies and runs the Astro build, the other copies just the static output into a minimal nginx image that serves it. That nginx container sits behind Traefik, which handles TLS and routing, on infrastructure I manage directly rather than a platform-as-a-service. The only runtime piece on top of the static build is the newsletter signup, which posts directly to Brevo rather than through a server route of my own.
Bemo (Bemo’s buying guide platform) has been running in production since 2026. I built it and I still maintain and operate it.


