Astro in production: what we learned shipping content sites on it
Candid notes from shipping client sites on Astro + Cloudflare: content collections, islands, build-vs-runtime tradeoffs, and the sharp edges nobody mentions.
We’ve written before about why our stack is Astro + Tailwind + Cloudflare and why Cloudflare specifically. Those posts are the sales pitch, more or less. This one is the engineering debrief: what actually happened after we committed to Astro for real client work, including the site you’re reading right now.
Short version: the bet paid off. Longer version: it paid off for specific reasons that only apply to specific kinds of projects, and there are sharp edges we hit that the framework’s marketing won’t tell you about. If you’re an owner deciding what your next site should be built on — or a developer deciding whether to trust the hype — the details below are the honest picture.
The core idea, and why it fits client work
Astro’s premise is that a website is mostly content, and content should ship as HTML. Not a JavaScript application that renders HTML after it loads — actual HTML, generated once at build time, served from a CDN.
That sounds like a throwback to 2005, and in a sense it is. The difference is that you write the site with modern tooling — components, TypeScript, a real build pipeline — and the output is old-fashioned static files. You get the developer experience of a modern framework and the delivery characteristics of a folder of HTML.
For the work we do — marketing sites, service pages, local landing pages, this blog — that’s almost exactly the right trade. A visitor from Lincoln searching for a web developer doesn’t care about our build pipeline. They care that the page appears instantly on their phone. Static HTML from an edge node a few milliseconds away is the most reliable way we know to make that happen, and it doesn’t degrade when a third-party script has a bad day.
Content collections earn their keep
The single most valuable Astro feature in production, for us, isn’t performance. It’s content collections.
A content collection is a folder of content — blog posts, city pages, case studies — with a schema attached. Every entry must have a title, a date, a description of the right shape, the right category from an allowed list. If a post is missing its meta description, the build fails. Nothing ships.
That sounds bureaucratic until you’ve run a site with 30+ pages for a while. Without enforcement, content drifts: someone forgets a description, a date gets typed in the wrong format, a tag gets spelled three ways. Each mistake is invisible until it costs you — usually in search, weeks later, where you can’t see the error directly. With collections, the whole class of problem is gone. The site literally cannot build in an inconsistent state.
The second-order benefit: because content is typed data, everything downstream is generated instead of maintained. Our sitemap, our RSS feed, our llms.txt file for AI crawlers, the related-posts links, the FAQ structured data on every post — all of it is derived from the collections at build time. When we add a page, six other things update themselves. On a hand-maintained site, those six things are six chances to forget.
Islands: JavaScript on purpose, not by default
Astro’s other headline feature is the islands architecture. The page is static HTML; individual components can opt in to being interactive “islands” that load their own JavaScript.
In practice, this changed how we think about interactivity. On an app framework, everything is JavaScript, so adding one more interactive widget is free — you’re already paying for the runtime. On Astro, each island is a visible, deliberate cost. You find yourself asking: does this actually need to be a React component, or is it a <details> element? Does this need to hydrate on page load, or only when it scrolls into view?
Our rough decision ladder, hardened by a couple dozen real decisions:
- Plain HTML and CSS first. Accordions, tabs-that-are-really-links, hover states, simple menus. Modern HTML covers more than most developers assume.
- A small vanilla script second. A copy-to-clipboard button or a mobile nav toggle doesn’t justify a framework. Twenty lines of script, no dependency, nothing to hydrate.
- A React island third, and only when there’s real state to manage. Our in-page product demos and the “Can AI help?” widget on /ai are React, because they hold conversation state, talk to a backend, and re-render meaningfully. That’s what the framework is for.
The result on this site: most pages ship effectively zero JavaScript, and the pages that do ship it, ship only the component that needs it. Nobody downloads chat-widget code on a page with no chat widget.
Build time vs. runtime: where each one belongs
The most useful mental model we’ve settled on: decide at build time everything you can, and push to runtime only what you must.
| Concern | Where it runs | Why |
|---|---|---|
| Pages, posts, city pages | Build time | Content changes rarely; HTML is fastest and can’t error at 2 AM |
| Sitemap, RSS, structured data | Build time | Derived from content; regenerate on every build |
| Contact form handling | Runtime (Worker) | Needs to send email and write to a database |
| Chat / AI scoping widget | Runtime (Worker + D1) | Genuinely dynamic conversation, per-visitor state |
| Analytics events | Runtime (Worker + D1) | Data arrives from visitors; can’t exist at build time |
The static side deploys as files on Cloudflare Pages. The dynamic side runs as Workers with D1 behind them. The boundary is explicit, which is the point: everything on the left of that line has no server to crash, no dependency to patch, no cold start. Only the right side needs monitoring, and it’s a small, well-fenced surface.
One honest consequence of static-first: updating content requires a rebuild. If a post is scheduled to publish Tuesday, the HTML that includes it doesn’t exist until something triggers a build on Tuesday. We handle that with scheduled builds, but it’s a real operational detail that server-rendered sites don’t have. If your site changes many times a day — inventory, pricing, listings — static-first is the wrong default and you should scope that honestly up front.
The sharp edges we actually hit
Every one of these cost us a real debugging session, so you don’t have to trust the brochure:
- Scoped styles are scoped harder than you expect. Astro scopes a component’s CSS to that component’s own markup. HTML that arrives any other way — rendered from Markdown, injected by a script — doesn’t get the scoping attributes, so your styles silently don’t apply. The fix (
is:global, or styling from a layout level) is easy; diagnosing it the first time is not, because nothing errors. The page just looks subtly wrong. - Inline scripts and processed scripts behave differently. Astro bundles and can hoist your script tags, which changes when they run relative to the DOM. A
getElementByIdthat worked in one component can return null after a refactor moves the element. The lesson: treat script/DOM timing as something to verify, not assume. - Props crossing into islands must be serializable. The static side and the interactive side are genuinely separate worlds; you can’t pass a function or a live object into an island. You feel this the first time you try to hand a callback to a React component and get a cryptic error. It’s a fair constraint — it’s why the architecture works — but it shapes your component design.
- A clean build is not a correct page. This one is on us, not Astro, but static generation makes it seductive: the build passes, so ship it. We’ve been burned by pages that built fine and rendered wrong. House policy now is to verify rendered output — actual screenshots of the worst-case pages — before anything deploys, the same way tests are non-negotiable on the code itself.
None of these are disqualifying. All of them are the kind of thing you only learn by shipping, which is roughly the thesis of this post.
When we don’t reach for Astro
Being candid about the boundaries:
- Real applications. If users log in and work in the thing — dashboards, portals, internal tools — we build a React app, sometimes with no Astro involved at all. When 90% of the page is interactive, islands stop being an optimization and start being ceremony.
- Owner-edited content. Astro content lives in files in a repository. That’s a feature for us (versioned, reviewable, schema-checked) and a wall for a client who wants to edit their own pages. That’s solvable with a CMS in front, but it’s added scope and we say so in the quote rather than discovering it in week six.
- Sites that change constantly. As above — if your content changes hourly, the rebuild model is friction. Runtime rendering exists for a reason.
You probably don’t need this framework specifically, either. A five-page site that changes once a year would be fine as plain HTML. What you need is a builder who picks the boring, fast option on purpose — Astro happens to be our current version of that for content sites, and we’ll swap it the day something serves clients better.
What this means if you’re buying, not building
If you’re a business owner rather than a developer, the takeaways compress to three things:
- Ask what the site ships to the browser. “Modern framework” can mean a fast static page or a half-megabyte JavaScript bundle pretending to be one. The difference shows up in your mobile load times and your search rankings.
- Ask what happens when content is wrong or missing. A build that refuses to ship an inconsistent site is worth real money over the life of the site. Hand-maintained consistency always drifts.
- Ask what the ongoing hosting and maintenance actually costs. Done right, a content site in 2026 should cost almost nothing to host and very little to keep alive. Recurring fees should map to actual ongoing work, not to keeping a fragile stack upright.
This is the stack behind our own work, and marketing sites built this way land in the $8K–$25K range at flat-bid pricing — the number is quoted before work starts, and the process includes the rendered-output verification and testing described above. If you’re weighing a rebuild and want a straight answer on whether this approach fits your situation — including “your current site is fine, keep it” — get in touch. We’re at Stanford Ranch in Roseville, and the first conversation costs nothing.
Related reading: our modern web stack · why we build on Cloudflare · how a boutique studio tests software · what web budgets actually buy in Roseville
FAQ
Frequently asked questions.
The questions clients ask most after reading this.
What is Astro, in one paragraph?
Is Astro good for a small-business marketing site?
When is Astro the wrong choice?
What are Astro content collections and why do they matter?
Does Astro work with React?
How does Astro compare to WordPress for performance?
What does an Astro site cost to host?
Who helps small businesses near Rocklin and Roseville, CA build sites like this?
More development reading
Related from the lab.
Development
Quantum readiness for a small business in 2026: what actually matters
No, you don't need to buy anything. For a Sacramento-area small business, quantum readiness in 2026 is mostly handled by modern TLS - plus one honest question.
6 min
Development
Edge databases explained: what Cloudflare D1 means for your app's speed and cost
What 'SQLite at the edge' actually means, where Cloudflare D1 beats a traditional database, where it falls over, and how it changes build and run costs.
7 min
Development
Why your small-business website should be boring — and fast
A page that loads in under 2 seconds beats a hero animation every time. Why fast, clear, 'boring' sites out-earn flashy ones for local businesses.
7 min