← Blog

Mastering Astro and Vite

2024-01-20
astro
vite
frontend
performance
typescript

Mastering Astro and Vite

Astro has quietly become one of the most interesting frameworks for content-heavy sites. Its islands architecture is a genuine paradigm shift — most of your page is static HTML, with interactive React/Vue/Svelte components hydrated only where needed.

Why Astro Changes the Default

Every other framework starts from the assumption that JavaScript is the default. Astro inverts this: HTML is the default, JavaScript is opt-in.

The practical result: a typical blog or marketing page ships zero kilobytes of framework JavaScript. The client doesn’t need to hydrate the entire component tree just to display text.

Islands Architecture Explained

An “island” is a component that requires client-side interactivity. Everything else is static.

---
// This component is server-rendered — zero JS shipped
import StaticCard from '@/components/StaticCard.astro';

// This component ships JavaScript
import InteractiveWidget from '@/components/InteractiveWidget.tsx';
---

<StaticCard title="No JS here" />
<InteractiveWidget client:visible />

The client: directives control when the island hydrates:

DirectiveWhen it hydrates
client:loadImmediately on page load
client:idleWhen browser is idle
client:visibleWhen scrolled into view
client:mediaWhen a media query matches

Vite Under the Hood

Astro uses Vite as its build tool and dev server. This means you get:

  • Native ESM in development — no bundling, instant HMR
  • Rollup-powered production builds — tree-shaking, chunk splitting
  • First-class TypeScript — no extra configuration required

The Vite config in Astro is exposed via astro.config.mjs:

import { defineConfig } from 'astro/config';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  vite: {
    plugins: [tailwindcss()],
    // Full Vite config access here
  },
});

Content Collections: Type-Safe Markdown

Astro’s content collections give you Zod-validated frontmatter with full TypeScript inference:

import { defineCollection, z } from 'astro:content';

const blog = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    date: z.string(),
    tags: z.array(z.string()),
  }),
});

Now getCollection('blog') returns fully typed entries — no more typos in frontmatter keys.

Performance Results

Running Lighthouse on an Astro site versus the equivalent Next.js version:

  • FCP: 0.4s vs 1.2s
  • TTI: 0.5s vs 2.8s
  • Total JS: 12 KB vs 180 KB

The numbers speak for themselves for content-focused pages.

When Not to Use Astro

Astro shines for content sites. For highly interactive applications (rich dashboards, real-time collaborative tools), Next.js or SvelteKit may be better fits. Know your use case.

Getting Started

npm create astro@latest my-site
cd my-site
npm run dev

The setup wizard is excellent and worth running through even if you know what you’re doing.