I just shipped blueprintcalc.com — 12 home improvement calculators (paint, concrete, drywall, deck, mulch, tile, etc.) that compute material quantities AND output a full shopping list with prices and retailer links.
I'd defaulted to Next.js for static-ish sites for years. This time I went with Astro and don't regret it. Here's the framework comparison and what actually mattered.
The constraints
Before picking a framework, I wrote down what the site had to do:
- ~95% of traffic will come from Google long-tail search. ("how much paint for 12x14 room", "concrete bag calculator 4 inch slab" — that kind of query.) SEO is everything.
- Lighthouse 95+ on mobile or I lose ranking. Google's Core Web Vitals are not optional for a content site competing with established players (Calculator.net, OmniCalculator).
- Calculator UI must be interactive — change inputs, see results update — without round-tripping to a server.
- No backend. No database. No auth. Every calculation is pure math on numeric inputs.
- Static-deployable to Cloudflare's free tier (zero hosting cost while traffic is small).
Notice what's NOT on the list: real-time features, server-side rendering of personalized content, complex routing, API routes. The site is essentially 12 interactive widgets embedded in 37 SEO-optimized content pages.
Why Next.js was the wrong shape
Next.js is excellent for app-shaped products: dashboards, SaaS, anything with auth + dynamic data. For a content site with islands of interactivity, it's overkill in ways that hurt:
Bundle size. Next.js ships React + the Next runtime + page-level hydration scaffolding by default. Even with aggressive code splitting, a "simple" Next page lands at ~80kb of JS minified. Multiply by 37 pages and you're paying for a runtime you barely use.
Hydration. Next hydrates the entire page tree on load, even sections that don't need interactivity (FAQ, formula explanation, related-calc links — all static markup in my case). That's wasted JS execution on every page load.
SSG with a runtime. Next.js can produce static HTML with output: 'export', but the JS bundle still ships React. You're paying React's cost without using its dynamism.
I tried it anyway in a small prototype. The paint calculator page came in at 94kb of JS for what should have been ~3kb of input handling. Lighthouse mobile was 81. That's a no for SEO.
What Astro does differently
Astro's pitch is "ship zero JavaScript by default, opt-in per island."
Concretely, this is the architecture:
- Pages are written in
.astrofiles, which look like JSX/HTML but compile to plain HTML at build time. - Any interactive component you want to ship (in any framework — React, Svelte, Vue, or vanilla JS) is wrapped as an "island" with a
client:load,client:idle, orclient:visibledirective. - The rest of the page ships as static HTML with no JS.
For my site, that means the calculator widget gets ~3kb of JS to handle input changes, and the FAQ / formula / related calculators sections ship zero JavaScript. Total per-page JS is roughly 4-6kb depending on the calc.
Lighthouse mobile lands consistently at 95+. First Contentful Paint under 1.2s on simulated 4G.
The calc engine pattern
Each calculator is a TypeScript module that exports two things — its input schema and a pure compute function:
// src/calculators/paint.ts
export const fields: Field[] = [
{ id: 'length', label: 'Room length (ft)', type: 'number', default: 12 },
{ id: 'width', label: 'Room width (ft)', type: 'number', default: 12 },
{ id: 'height', label: 'Wall height (ft)', type: 'number', default: 8 },
{ id: 'coats', label: 'Number of coats', type: 'number', default: 2 },
// ...
];
export function compute(inputs: Inputs): Result {
const wallArea = 2 * (inputs.length + inputs.width) * inputs.height;
const coverage = 350; // sq ft per gallon
const gallonsNeeded = Math.ceil((wallArea * inputs.coats) / coverage);
return {
summary: `${gallonsNeeded} gallons of paint`,
materials: buildShoppingList(gallonsNeeded, inputs),
};
}
A single <Calculator> Astro component reads the fields array to render labeled inputs and binds compute() to recalculate on every change. URL state is synced via query params so results are shareable / bookmarkable.
Adding a 13th calculator is roughly:
- ~150 lines of TypeScript (math + shopping list logic)
- A test file
- A page entry that imports the calc and points the layout at it
That's it. No framework friction.
SEO: schema.org markup is the unlock
This is the part I underestimated. Every page emits three types of structured data via <script type="application/ld+json">:
-
WebPageschema (basic page metadata) -
HowToschema for the formula explanation (gets you "How to" rich results in Google) -
FAQPageschema for the FAQ section (gets you accordion rich results inline below your search snippet)
Astro makes this trivial — schema is just a function that returns a JSON object, and you inject it as a script tag in the layout's <head>. No framework-specific helper, no plugin.
Without this markup, even a perfectly-optimized page is just a blue link. With it, Google often shows the FAQ inline below your title, which dramatically increases CTR.
Where Next.js would still win
To be fair: if any of these were on my requirements list, I'd have stayed with Next.js.
- User auth + per-user data — Astro can do server endpoints, but Next.js's app router is much more ergonomic for authenticated UIs.
- Real-time / streaming UIs — Next.js Suspense + RSC are genuinely good for this.
- Heavy form-driven app surfaces — pages with 20+ stateful form fields, multi-step wizards, etc. Astro can do them but you're fighting the framework.
- Embedded as part of a larger React monorepo — interop is painful enough that it's usually not worth fighting.
For static, SEO-driven content sites with islands of interactivity, Astro is the right tool. For app-shaped products, it isn't.
The numbers
After ~3 weeks of building:
- 12 calculators, 37 pages, 124 unit tests (Vitest)
- Lighthouse: 96 mobile, 99 desktop on the heaviest page
- Per-page JS: ~4-6kb minified+gzipped (calculator interactivity)
- Build time: 2 seconds for the full site
- Hosting cost: $0/month on Cloudflare Workers + Static Assets
- Total infra cost: $10/yr (just the domain)
Site is at blueprintcalc.com if you want to poke at it.
What I'd do differently
If I were starting over: nothing fundamental. Astro was clearly right for this shape of project. The thing I wasted time on was over-engineering the calculator engine in week 1 — I built a generic fields[] schema before I had two calculators to compare. Two would have been enough to find the right abstraction; I built it from one.
Lesson: don't generalize until you've copy-pasted the same thing at least twice.
If you've built content/SEO sites with Astro and have war stories on programmatic page generation or schema markup gotchas, I'd love to hear them. Currently in the indexing-and-waiting phase before applying for AdSense — open to feedback on the build or the launch strategy.
Top comments (0)