Back to Blog

Building Modern Web Apps with Astro

Building Modern Web Apps with Astro

The web has come a long way. We’ve gone through jQuery, Angular, React SPAs, and now we’re circling back to something that feels right — shipping less JavaScript.

After building dozens of client projects and internal products, we settled on Astro as our default choice for content-driven websites. Not because it’s trendy, but because it solves the actual problems we kept running into: bloated bundles, slow initial loads, and over-engineered stacks for sites that are fundamentally about content.

Why Astro?

Astro takes the best ideas from the last decade of web development and strips away the bloat. You get component-based architecture, but the output is plain HTML and CSS by default. JavaScript only ships when you need interactivity.

For content-driven sites — blogs, portfolios, marketing pages — this is exactly what you want. Your users get fast pages, search engines get clean markup, and you get a great developer experience.

Consider what happens with a typical React-based portfolio site. The browser downloads React, ReactDOM, your router, your state management library, and your component tree — all before the user sees a single word of content. We’re talking 80-150 KB of JavaScript minimum, gzipped, for what is essentially a static page. Astro ships zero JavaScript for the same page by default. The difference in Lighthouse performance scores is dramatic: we routinely see 95-100 on Astro sites versus 60-80 on equivalent React SPAs.

When we rebuilt the Trackelio marketing site, we moved from a Next.js setup to Astro. The result was a 73% reduction in total JavaScript transferred and a Time to Interactive improvement from 3.2 seconds to 0.8 seconds on a throttled 3G connection. Those numbers matter for conversion rates and SEO rankings alike.

Web application interface displayed in a modern browser window

The Island Architecture

What makes Astro unique is its island architecture. Instead of hydrating the entire page with JavaScript, you selectively hydrate only the interactive parts. A static blog post with a single interactive comment section? Only the comment section ships JavaScript.

This isn’t just a performance optimization — it’s a fundamentally different way of thinking about web pages.

Here’s how it looks in practice. Say you have a page with a static hero section, a static features grid, and an interactive pricing calculator at the bottom:

---
import Hero from '../components/Hero.astro';
import Features from '../components/Features.astro';
import PricingCalculator from '../components/PricingCalculator.tsx';
---

<Hero />
<Features />
<PricingCalculator client:visible />

The Hero and Features components render to static HTML at build time. Zero JavaScript. The PricingCalculator is a React component that only hydrates when it scrolls into the viewport, thanks to client:visible. If the user never scrolls that far, the JavaScript never loads.

Astro gives you several hydration directives to control exactly when interactive components load:

  • client:load — Hydrate immediately on page load. Use for above-the-fold interactive elements.
  • client:idle — Hydrate once the browser is idle. Good for lower-priority interactions.
  • client:visible — Hydrate when the component enters the viewport. Perfect for below-the-fold content.
  • client:media — Hydrate only when a CSS media query matches. Useful for mobile-only interactions.

This level of control is something no other framework offers out of the box. You make intentional decisions about what JavaScript ships and when, rather than shipping everything and hoping tree-shaking catches the rest.

Content Collections and Type Safety

One of Astro’s strongest features for content-driven sites is Content Collections. You define a schema for your content using Zod, and Astro validates every markdown file against it at build time.

// src/content/config.ts
import { defineCollection, z } from 'astro:content';

const blog = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.coerce.date(),
    updatedDate: z.coerce.date().optional(),
    author: z.string().default('Threshline'),
    tags: z.array(z.string()),
    heroImage: z.string().optional(),
  }),
});

export const collections = { blog };

This catches errors at build time rather than in production. A missing title, a malformed date, a tag that’s a string instead of an array — all caught before the site deploys. When you have 50+ blog posts or case studies, this kind of validation prevents entire categories of bugs.

We use this pattern on every content-heavy project. For the MindHyv documentation site, Content Collections let us enforce consistent structure across 100+ pages of technical docs, with type-safe frontmatter for versioning, category hierarchies, and cross-references.

Frontend development code displayed in a code editor with syntax highlighting

Our Stack

For most of our projects, we pair Astro with:

  • Content Collections for type-safe markdown
  • CSS custom properties for theming
  • Cloudflare Pages for deployment
  • View Transitions API for smooth page navigation
  • Sharp for automatic image optimization at build time

This stack deserves some explanation.

CSS custom properties over Tailwind? For content sites, yes. Tailwind is excellent for application UIs, but for a marketing site or blog, a clean set of design tokens in custom properties keeps the HTML readable and the CSS maintainable. We define a set of tokens — colors, spacing, typography — and reference them throughout. No utility class soup, no purging configuration.

:root {
  --color-surface: #0a0a0a;
  --color-text: #fafafa;
  --color-accent: #3b82f6;
  --font-body: 'Inter', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', monospace;
  --max-width: 72rem;
  --spacing-page: clamp(1rem, 4vw, 3rem);
}

Cloudflare Pages gives us global edge deployment with zero configuration. Push to main, and the site is live worldwide in under 45 seconds. The free tier handles most client sites comfortably, and the Workers integration means we can add server-side functionality — form handling, API proxies, authentication — without switching platforms.

View Transitions are a native browser API that Astro integrates seamlessly. Adding <ViewTransitions /> to your layout gives you smooth, animated page transitions without a client-side router. The pages are still server-rendered HTML — you get SPA-like navigation feel with MPA architecture. It’s the best of both worlds.

When Astro Isn’t the Right Choice

We’re not dogmatic about tools. Astro excels at content-driven, mostly-static sites. For highly interactive applications — real-time dashboards, complex form workflows, collaborative editors — a full React or SvelteKit setup makes more sense.

When we built LancerSpace, a platform with real-time collaboration features and complex state management, we reached for SvelteKit instead. The application was fundamentally interactive, with WebSocket connections, optimistic UI updates, and shared cursors. Astro’s island model would have meant making nearly every component an island, defeating the purpose.

The decision framework is straightforward: if more than 60-70% of your page surface is interactive, use a full application framework. If the majority is content with pockets of interactivity, Astro wins.

JavaScript and web technology code on a developer workstation

Performance in Practice

Numbers from recent Astro projects we’ve shipped:

MetricBefore (React SPA)After (Astro)
First Contentful Paint1.8s0.4s
Time to Interactive3.2s0.8s
Total JS Transferred187 KB12 KB
Lighthouse Performance7298

These aren’t cherry-picked. They’re representative of what happens when you stop shipping a JavaScript runtime for pages that don’t need one.

Google’s Core Web Vitals directly impact search rankings. A site that loads in under a second on mobile has a measurable advantage over one that takes three seconds. For client projects where organic traffic matters — which is most of them — this performance gap translates directly to business results.

Conclusion

Astro isn’t magic. It’s the logical outcome of realizing that most web pages are fundamentally documents, not applications. By defaulting to zero JavaScript and letting you opt in to interactivity where you need it, Astro aligns the framework’s architecture with what the browser actually does well: rendering HTML fast.

We’ve shipped enough projects with Astro to know where it shines and where it doesn’t. For content-driven websites — and that includes most marketing sites, portfolios, blogs, and documentation — it’s the best tool available right now. The developer experience is excellent, the output is fast, and the architecture makes you think carefully about what actually needs to be interactive.

If you’re building a content site and reaching for Next.js or Nuxt out of habit, give Astro a serious look. The official documentation is some of the best in the ecosystem, and you can have a production-ready site deployed in an afternoon.