T2 — Intermediate · Comprehensive

Ship less.
Build faster.
Meet Astro.

The framework that decided HTML was good enough — and then made it a superpower. Here's what Astro is, why it exists, and why the .astro file format is unlike anything else in the web ecosystem.

T2 / Intermediate Framework Overview The .astro Format Deployment Guide
scroll to begin
01 / What Is Astro

The framework that dared to ship nothing

Every JavaScript framework of the last decade shared a quiet assumption: the browser needs to run code to build your UI. React, Vue, Angular — they all ship a runtime to the browser, execute it on load, and construct the page in JavaScript. For interactive apps, this makes sense. For a website that's mostly text, images, and links? It's like renting a bulldozer to plant a flower.

Astro was built on a different premise. Your HTML already is the UI. Generate it once at build time — or on the server when needed — and send it over the wire. No framework runtime. No hydration step. Just fast, static HTML that loads instantly.

💡 Mental Model

Think of traditional JS frameworks as a restaurant that sends you raw ingredients and a chef's instructions — you cook at your table. Astro is the restaurant that sends you the finished meal. Less work for the browser, faster on your plate.

What Astro actually does

Astro is a web framework for content-driven websites — marketing sites, blogs, documentation, portfolios. It:

🏗️

Builds static HTML

At build time, Astro renders every page to plain HTML. No client-side JavaScript required to display the page.

🏝️

Island Architecture

Any component that needs interactivity becomes an "island" — only that part hydrates. The rest stays static HTML.

🧩

Framework-agnostic

Use React, Vue, Svelte, Solid, Preact — or none. Mix and match in the same project.

📦

Content Collections

Type-safe content from Markdown files. Astro validates your frontmatter schema with Zod at build time.

Zero JS by default

A fresh Astro page ships 0 bytes of JavaScript to the browser. Every KB must be explicitly opted into.

🔄

Server Rendering

Switch any page — or the whole site — to server-side rendering when you need dynamic content or auth.

⚡ Why It Matters

Google's Core Web Vitals directly affect search ranking. Shipping less JavaScript means faster LCP (Largest Contentful Paint) and better FID (First Input Delay). A default Astro page routinely scores 100/100 on Lighthouse. That's not luck — it's the architecture.

02 / The Competition

Where Astro sits in the ecosystem

The web framework space is crowded. Before you can appreciate what makes Astro different, you need to know who else is in the room.

Framework Base Language Zero JS? Best For Trade-off
Astro Any (agnostic) Yes Content sites, marketing, docs, blogs Less suited for heavy full-stack apps
Next.js React No Full-stack apps, e-commerce, dashboards React bundle always ships; opinionated toward Vercel
Nuxt Vue No Full-stack Vue apps, hybrid rendering Tied to Vue; complex config
SvelteKit Svelte No Fast, elegant full-stack apps Tied to Svelte; smaller ecosystem
Gatsby React + GraphQL No Static sites with rich data sources Slow builds; full React + GraphQL always ships
Eleventy (11ty) Templates (Nunjucks, Liquid...) Yes Pure static HTML, minimal config No component model; templating only
Hugo Go templates Yes Massive content sites, fastest build times Go template syntax is its own world; no JS integration

The honest take

Choose Astro when...

  • Your site is mostly content — text, images
  • Performance and Lighthouse scores matter
  • You want React or Vue without shipping their runtime
  • You're building a blog, docs site, or marketing page
  • You want TypeScript and a great DX out of the box

Choose Next.js when...

  • You're building a full application (auth, dashboards, CRUD)
  • Your team is already deep in React
  • You need server-side logic on most pages
  • You want the largest ecosystem and community
  • You're using Vercel and want the tightest integration
→ Pro Tip

Astro and Next.js aren't really competitors — they optimize for different problems. Astro is a publishing tool. Next.js is an application tool. If your site has more pages than user states, Astro. If it has more user states than pages, Next.js.

03 / Core Features

The ideas that make Astro different

Islands Architecture

This is Astro's signature idea. In a traditional React app, the entire page is a React tree — everything hydrates, everything re-renders. In Astro, the default is static HTML. You opt specific components into interactivity using client directives.

example.astro — Islands in action
---
import Header  from './Header.astro';  // static — zero JS
import Counter from './Counter.jsx';   // a React component
import Map     from './Map.svelte';     // a Svelte component
---
<Header />                          <!-- static HTML, 0 bytes JS -->
<Counter client:load />           <!-- hydrates immediately -->
<Map     client:visible />        <!-- hydrates when scrolled into view -->
<Chart   client:idle />           <!-- hydrates when browser is idle -->
Directive When It Hydrates Best For
client:loadImmediately on page loadCritical interactive UI (nav, forms)
client:idleWhen browser is idle (requestIdleCallback)Lower-priority widgets
client:visibleWhen component enters the viewportBelow-the-fold interactive elements
client:mediaWhen a CSS media query matchesMobile-only or desktop-only components
client:onlyClient-side only — never server-renderedComponents that break on the server

Content Collections

If you're building a blog or docs site, content collections are a game-changer. You define a schema for your Markdown files, and Astro validates every file against it at build time. Missing a required field? Build error. Wrong type? Build error.

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

const blog = defineCollection({
  type: 'content',
  schema: z.object({
    title:       z.string(),
    publishDate: z.date(),
    author:      z.string().default('Anonymous'),
    tags:        z.array(z.string()).optional(),
    draft:       z.boolean().default(false),
  }),
});

export const collections = { blog };

Rendering Modes

Static (default)

  • All pages rendered at build time
  • Output: plain HTML/CSS/JS files
  • Deployable to any CDN or static host
  • Fastest possible load times

Server (SSR)

  • Pages rendered on each request
  • Can read cookies, auth headers, databases
  • Requires a server or edge function
  • Use for dashboards, auth flows
→ Pro Tip

Add export const prerender = false to any page to opt it into SSR while keeping everything else static. This "hybrid" mode is Astro's secret weapon — 95% of your site ships as static HTML, while your /dashboard renders on demand.

View Transitions

Astro 3+ ships built-in View Transitions — smooth, SPA-like page animations with a single import. Add <ViewTransitions /> to your layout and Astro uses the browser's native View Transitions API, with a polyfill fallback. You get the feel of a single-page app without the architecture of one.

04 / Deployment

Cloudflare vs Vercel — the honest comparison

Once you've built your Astro site, you need somewhere to deploy it. The two most popular choices are Vercel and Cloudflare Pages — and the decision matters more than you'd think, especially if you're using SSR.

💡 Mental Model

Both are CDN-first deployment platforms. The key difference is in their serverless runtime: Vercel runs Node.js in AWS Lambda functions at the edge. Cloudflare runs V8 isolates (Workers) across 300+ locations. Same concept — very different environments.

For static Astro (no SSR)

If your Astro site is fully static, the choice is nearly academic. Both give you:

Both platforms give you

  • Global CDN distribution
  • Automatic HTTPS with your domain
  • Git-based continuous deployment
  • Preview deploys on every PR
  • Generous free tiers

Cloudflare's free-tier edge

  • Unlimited bandwidth (no overage fees ever)
  • 500 builds/month free vs 100 on Vercel
  • 300+ edge locations (Vercel: ~50)
  • Workers KV, D1, R2 ecosystem available
  • No cold starts on Workers runtime

For SSR Astro — this is where it gets interesting

When you add server-side rendering, you need an adapter. Astro ships official adapters for both:

astro.config.mjs
// For Cloudflare Pages
import cloudflare from '@astrojs/cloudflare';
export default { adapter: cloudflare() };

// For Vercel
import vercel from '@astrojs/vercel/serverless';
export default { adapter: vercel() };
⚠️ Watch Out

Cloudflare Workers do not run Node.js. They run the V8 JavaScript engine directly, without Node's standard library. Packages using fs, path, or Node's crypto will break on Cloudflare. This is the #1 gotcha for developers moving from Vercel to Cloudflare.

Feature Vercel Cloudflare Pages
RuntimeNode.js (AWS Lambda)V8 isolates (Workers)
Node.js compatFullPartial (node: prefix required)
Cold start~200–500ms<5ms (no cold starts)
Edge locations~50300+
Free tier bandwidth100 GB/monthUnlimited
Preview deploysExcellentGood
Built-in databaseNone (use external)D1 (SQLite at edge)
Object storageNone (use external)R2 (S3-compatible, free egress)
KV storeNone (use external)Workers KV (globally replicated)

The verdict

Choose Cloudflare when...

  • Your site is fully or mostly static
  • Global edge performance is the priority
  • You want the Workers ecosystem (D1, R2, KV)
  • Bandwidth costs are a concern
  • You're comfortable with non-Node.js runtime limits

Choose Vercel when...

  • Your SSR code uses Node.js APIs or packages
  • You want the smoothest developer experience
  • Preview deploys and PR comments are critical
  • You depend on npm packages that assume Node.js
  • You're already in the Next.js / Vercel ecosystem
05 / The .astro Format

What makes an .astro file tick

Every component and page in an Astro project lives in a .astro file. It looks like HTML. It mostly is HTML. But it has three superpowers that plain HTML never had: a build-time script, a component model, and automatic CSS scoping.

Anatomy of an .astro file

An .astro file has up to four distinct zones, each with a different purpose and a different runtime:

Zone 1 — Component Script (Frontmatter)

Wrapped in --- fences. Full TypeScript/JavaScript that runs at build time or on the server — never in the browser. This is where you import components, fetch data, define variables, and validate props.

Zone 2 — Template (HTML)

Standard HTML with JSX-style {expressions} for dynamic values. Supports conditional rendering and .map() for lists. The output is always plain HTML — no virtual DOM, no runtime.

Zone 3 — <style> (Scoped CSS)

CSS that Astro automatically scopes to this component by injecting a unique attribute. Styles don't bleed into children or siblings. Use :global() to intentionally break out of scoping.

Zone 4 — <script> (Client JavaScript)

JavaScript that runs in the browser. Astro bundles and deduplicates these automatically. Add is:inline to skip bundling. This is entirely separate from Zone 1 — it ships to the client, Zone 1 does not.

A complete annotated example

src/components/PostCard.astro
---
// Zone 1: runs at build time — never shipped to the browser
interface Props {
  title: string;
  date:  Date;
  href:  string;
}

const { title, date, href } = Astro.props;
const formatted = date.toLocaleDateString('en-US', {
  month: 'long', year: 'numeric'
});
---

<!-- Zone 2: HTML template with expressions -->
<article class="card">
  <time>{formatted}</time>
  <h2><a href={href}>{title}</a></h2>
  <slot />  <!-- renders children passed from the parent -->
</article>

<!-- Zone 3: scoped CSS — .card only applies to this component -->
<style>
  .card {
    background: #1a1a2e;
    border-radius: 8px;
    padding: 1.5rem;
  }
</style>

<!-- Zone 4: client script — this DOES ship to the browser -->
<script>
  document.querySelector('.card')
    ?.addEventListener('click', () => console.log('clicked'));
</script>

Key template syntax

Template syntax quick reference
{/* Expressions — any JS expression in curly braces */}
<p>{2 + 2}</p>                            <!-- renders: 4 -->
<p>{name.toUpperCase()}</p>               <!-- renders: NAME -->

{/* Conditional rendering */}
{isLoggedIn && <Dashboard />}            <!-- short-circuit -->
{isLoggedIn ? <Dashboard /> : <Login />} <!-- ternary -->

{/* Rendering lists */}
{posts.map(post => (
  <PostCard title={post.title} href={post.slug} />
))}

{/* Spread props */}
<Button {...props} />

{/* Raw HTML — use carefully, XSS risk */}
<div set:html={markdownContent} />
⚠️ Watch Out

Astro templates use class= (like HTML), not className= (like React/JSX). This trips up React developers constantly. The rule: Astro templates are closer to HTML than to JSX. When in doubt, write it like HTML.

06 / .astro vs Everything Else

How .astro compares to the alternatives

Understanding what makes .astro unique requires knowing what problem each format was designed to solve — and where their boundaries lie.

.astro vs .html

.html — raw HTML

  • No logic — pure markup
  • No components — copy-paste everything
  • No variables or expressions
  • CSS is global by default
  • Served as-is; no build step needed

.astro — HTML with superpowers

  • Frontmatter for logic and data fetching
  • Full component model — import and compose
  • JavaScript expressions in templates
  • CSS is scoped to the component by default
  • Compiles to plain HTML at build time

The output of an .astro file is plain HTML. But the authoring experience is radically better — you get components, logic, and type safety without shipping a runtime to your users.

.astro vs .jsx / .tsx

💡 Mental Model

JSX runs in the browser. .astro runs at build time. React ships a runtime to construct your UI in the browser; Astro constructs the UI during the build and ships the finished HTML. This is why an Astro page has zero framework dependency at runtime — the work was already done before the user arrived.

Feature .astro .jsx / .tsx (React)
Renders whereBuild time / serverClient (browser) + optionally server
Ships a runtimeNoYes — React (~45 KB gzipped)
State managementNo (build-time only)Yes — useState, useReducer...
CSS scopingAutomaticManual (CSS Modules, styled-components...)
Attribute syntaxclass= (HTML-style)className= (JSX-style)
Lifecycle hooksNoneuseEffect, useMemo, etc.
Can import .astroYesNo

The rule of thumb: use .astro for structure, layout, and content. Use .jsx wrapped in a client:* directive when you need interactivity or state.

.astro vs .mdx

MDX is Markdown with JSX embedded — designed for prose that occasionally needs interactive components. .astro is for the structure that wraps that prose.

.mdx — prose with components

  • Markdown-first: write in readable prose
  • Embed JSX components mid-document
  • Great for blog posts, docs, tutorials
  • Content is the focus
  • Imports at the top; no frontmatter fences

.astro — structure with logic

  • HTML-first: write in markup
  • Embed any framework component via islands
  • Great for layouts, pages, UI components
  • Structure is the focus
  • Frontmatter handles all logic
→ Pro Tip

In an Astro project, both formats work together. Your blog layout is an .astro file. Your blog posts are .mdx files. The layout wraps the post. Structure and content stay cleanly separated — each format doing what it's best at.

The problem .astro uniquely solves

Every format before .astro forced a tradeoff:

.astro is the first format that gives you a full component model, TypeScript, logic, scoped CSS, and zero runtime cost — because all that work happens before the HTML ever reaches a browser.

⚡ Why It Matters

The web has drifted toward shipping megabytes of JavaScript to render content that could have been plain HTML. The .astro format is a direct response: a component format that takes complexity seriously at authoring time, so your users don't pay for it at load time.

Reference

Quick Reference

Client Directives

Islands — client:* directives
client:load Hydrate immediately on page load. Use for visible, critical UI.
client:idle Hydrate after main thread is idle. Use for lower-priority, below-fold UI.
client:visible Hydrate when component scrolls into viewport. Great for lazy widgets.
client:media="(query)" Hydrate when CSS media query matches. Mobile-only or desktop-only.
client:only="react" Skip SSR. Only renders in the browser. Specify the framework name.

Frontmatter Essentials

.astro frontmatter patterns
const { prop } = Astro.props Destructure props passed to this component from the parent.
Astro.url The current page URL. Access .pathname, .searchParams, etc.
Astro.cookies Read and set cookies. SSR only.
Astro.redirect('/path') Return a redirect Response. SSR only.
export const prerender = false Opt this page out of static generation; force SSR for this route only.

Deployment Adapters

SSR adapter packages
@astrojs/cloudflare Deploy to Cloudflare Pages + Workers. V8 runtime, no Node.js.
@astrojs/vercel/serverless Deploy to Vercel serverless functions. Full Node.js runtime.
@astrojs/node Self-host on any machine running Node.js.
go deeper · T3

What comes next

Natural T3 continuations of this topic — not live yet, but they're coming.

T3 · coming soon

Astro Content Collections Deep Dive

Typed front matter, Zod schemas, getCollection() — how to build a fully validated content pipeline with Astro's native system.

T3 · coming soon

Astro Islands Architecture

Partial hydration explained: what client:load, client:visible, and client:idle actually do and when to reach for each one.

T3 · coming soon

Static Site Build Pipelines

How SSG, SSR, and ISR differ, how bundlers fit in, and what Astro's build output actually looks like under the hood.