Technical note

Building enwefah.dev With Astro, Cloudflare Workers, and a Simple Architecture

A look at how I built this site as a static Astro project, why it ships very little JavaScript, and how its deployment and release checks stay maintainable.

enwefah.dev is a small website with a serious job: it needs to explain who I am, show selected public work, and give me a durable place to publish technical notes. It also needs to remain fast, accessible, easy to discover, and easy for me to maintain.

I chose a deliberately simple architecture around those requirements. Astro and TypeScript build the site into static files. GitHub Actions runs the quality gates. Wrangler deploys the result to Cloudflare Workers Static Assets. There is no database, content management system, authentication layer, or client-side application runtime.

The interesting part was not choosing a fashionable stack. It was deciding how little machinery the site needed without treating “small” as permission to skip production discipline.

Why I chose Astro

This is a content-first site. Most pages are text, links, and a few reusable layout components, so rendering a JavaScript application in every visitor’s browser would solve a problem I do not have.

Astro fits that shape well for me. Pages and Markdown content are rendered at build time, while components give the site a consistent structure. TypeScript and typed content collections catch missing or malformed frontmatter before publication. I use Markdown for the notes themselves, and MDX is available if a future article genuinely needs richer authoring.

Astro also makes browser JavaScript opt-in. The site can use components during the build without turning them into hydrated components in production. That distinction helped me preserve the editorial experience described on the about page: the content arrives as useful HTML, not as an empty shell waiting for an application to start.

Why a static architecture

Every public page is known when I build the site. There are no signed-in users, personalised views, or request-time data requirements. Static generation matches the product as it exists; it is not an optimisation I added afterwards.

That gives the site a short delivery path:

  1. Astro reads typed content and page templates.
  2. The build produces HTML, CSS, feeds, discovery files, and images.
  3. Cloudflare Workers Static Assets serves those files from the edge.

That leaves me with fewer failure modes than an application server backed by a database. It also gives search engines complete, crawlable HTML and keeps the canonical URL, metadata, headings, and contextual links visible without client-side rendering.

The trade-off is that publishing requires a build and deployment. For my personal site, maintained in version control, that is useful friction: I can review every change and check the output before it becomes public.

Deployment workflow

GitHub is the source of truth. Pull requests and changes to the main branch run a GitHub Actions quality workflow that installs the locked dependencies and validates the project. Production deployment is a separate, manually triggered workflow restricted to the main branch.

That workflow builds the same static output and uses Wrangler to deploy it to Cloudflare Workers. Preview and production use separate Worker configurations so preview pages can be marked noindex without risking that directive on the production site. Production also uses the slashless URL policy consistently across internal links, canonical tags, Open Graph URLs, RSS entries, sitemap entries, and redirects.

I chose a manual production trigger because this is a small site and I value a clear review point before publication. The cost is one extra release step. The benefit is that a passing commit does not automatically become a public content change.

Production practices for a small website

I want the release checks to cover more than whether Astro compiled. They inspect the generated site for duplicate metadata, broken internal links, draft leakage, sitemap and canonical mismatches, malformed HTML, unapproved browser scripts, and prohibited content. Browser tests cover representative responsive layouts and accessibility behaviour.

I made technical SEO part of the build rather than a later plugin. Astro generates the sitemap from real routes. The RSS endpoint uses the same published-note collection as the website. Article pages emit BlogPosting and breadcrumb structured data, and inherit a generated Open Graph image unless an article provides an approved override.

I use Google Search Console as part of the production feedback loop. After deployment, it can verify the domain, process the sitemap, inspect representative URLs, and reveal crawl or indexing problems that a local build cannot observe. I treat those checks as launch and post-launch work, not something the source code can claim to have completed on its own.

I took the same restrained approach to analytics. The site supports a consent-controlled GA4 setup, and Cloudflare Web Analytics can provide traffic and real-user performance signals at the edge. The Google tag is blocked until a visitor grants analytics consent. The local and generated-output checks verify that arrangement, while Cloudflare’s automatic beacon still has to be confirmed on the deployed domain.

Things intentionally avoided

I did not add a database, CMS, authentication system, server-side API, or general client application. None of them serves the current content model.

I also avoided decorative animation, large font downloads, a tag manager, and a collection of third-party widgets. The one first-party browser script has a narrow job: manage analytics consent. Keeping that allowlist explicit makes an unexpected script a build failure rather than a quiet performance or privacy regression.

I may revisit some of these choices if the product changes. If the site eventually needs authenticated tools, durable state, user-generated content, or background work, that would be a reason to reconsider the architecture. Adding those systems in anticipation would make today’s site harder to operate without improving it for visitors.

Lessons learned

My main lesson was that simple architecture still requires precise decisions. Static output does not automatically give a site correct canonicals, useful metadata, accessible navigation, safe previews, or honest analytics. I still had to design and test those properties.

I also found that constraints can improve the implementation. Treating browser JavaScript, dependencies, and dynamic infrastructure as costs forced each addition to have a clear purpose. The codebase is easier to understand because its boundaries match the website’s actual needs.

Finally, users and crawlers receive the generated output, not my source files. I need to check the source, but checking the built HTML, sitemap, feed, redirects, and scripts is what closes the gap between intention and production behaviour. For this small personal site, that discipline gives me more value than a more elaborate stack.

More engineering notes

  1. Choosing Cloudflare Workers for Lightweight SaaS InfrastructureWhy I chose Cloudflare Workers for small, web-facing products, where the platform reduces operational work, and where I would use a different approach.5 min read
  2. Building TaxCalc.ng: Engineering Lessons From Creating a Nigerian Tax Compliance PlatformWhat building TaxCalc.ng taught me about turning tax rules into understandable, dependable workflows without making the product more complex than it needs to be.5 min read