Content Framework

Astro

Astro is the content-first framework that ships zero JavaScript by default. Build pages as HTML and add interactivity only where you need it, with any UI library.

intermediate14 min readUpdated Sep 15, 2026
[slug].astro
astro
---
// src/pages/blog/[slug].astro
import { getCollection, render } from "astro:content";

export async function getStaticPaths() {
  const posts = await getCollection("blog");
  return posts.map((post) => ({
    params: { slug: post.id },
    props: { post },
  }));
}

const { post } = Astro.props;
const { Content } = await render(post);
---

<article>
  <h1>{post.data.title}</h1>
  <Content />
</article>
Default output
Static HTML, zero JS
Components
.astro files
Interactivity
Islands, opt-in per component
UI libraries
React, Vue, Svelte, Solid, Preact
Content
Content collections
Server
SSR and on-demand rendering

Why it matters

Why Astro is different

Zero JavaScript by default

Pages ship as HTML and CSS. JavaScript is added only for the components that explicitly ask for it.

Bring your own UI library

Use React, Vue, Svelte, Solid or Preact in the same project, and mix them page by page or even component by component.

Fast by construction

Content-first pages load quickly because there is nothing to hydrate, and only the interactive islands pay a cost.

The big picture

The three ideas behind Astro

HTML first, islands for interactivity, and a component format that runs on the server and ships nothing extra by default.

Server components

Rendering

.astro components run at build time or on the server and output plain HTML.

Islands

Interactivity

Client directives hydrate individual components on demand instead of the whole page.

Content collections

Content

Typed, validated content that powers blogs, docs and marketing sites.

Astro at a glance

What Astro gives you

.astro components

A frontmatter script, an HTML template and scoped styles in one file.

File-based routing

Files in src/pages become routes, with dynamic segments and layouts.

Framework islands

Drop React, Vue or Svelte components into an Astro page.

Client directives

client:load, client:visible, client:idle and client:only control hydration.

Content collections

Schema-validated markdown and data with type-safe queries.

SSR and adapters

Render on demand and deploy to Node, serverless or the edge.

A short history

From static site generator to content platform

  1. 2021

    Astro beta

    The zero-JavaScript-by-default framework attracts attention for its islands architecture.

    21
  2. 2022

    Astro 1.0

    A stable release with integrations for React, Vue, Svelte and more.

    22
  3. 2023

    Astro 2 and 3

    Content collections and view transitions arrive, strengthening the content story.

    23
  4. 2024

    Astro 5

    A new content layer, server islands and improved tooling.

    24
  5. Today

    A content-first favourite

    A leading choice for blogs, docs, marketing sites and hybrid apps.

    Today

The complete guide

Astro: Everything you need to know

What is Astro?

Astro is a content-first web framework built around a simple bet: most pages do not need much JavaScript. It renders pages to HTML by default, ships no client JavaScript unless you ask for it, and lets you add interactive components only where they matter.

That design makes Astro exceptionally good at the kinds of sites that make up much of the web: blogs, documentation, marketing pages, portfolios and content-heavy products. It is not anti-JavaScript — it is pro-restraint. You get the interactivity you need and nothing you do not.

.astro components

An Astro component is a file with a frontmatter script, a template and optional scoped styles.

---
// src/components/Card.astro
interface Props {
  title: string;
  href: string;
}

const { title, href } = Astro.props;
---

<a class="card" href={href}>
  <h3>{title}</h3>
  <slot />
</a>

<style>
  .card {
    display: block;
    border-radius: 1rem;
    padding: 1.5rem;
  }
</style>

The frontmatter runs on the server at build time or per request. The template is HTML with JSX-like expressions, and the <slot /> lets a parent pass children. Styles are scoped to the component by default. Because the component runs on the server, you can query databases, read files and call APIs directly in the frontmatter.

File-based routing

Files in src/pages become routes, and dynamic segments use brackets.

src/pages/
├── index.astro            # /
├── about.astro            # /about
├── blog/
│   ├── index.astro        # /blog
│   └── [slug].astro       # /blog/:slug
└── rss.xml.js             # /rss.xml

For dynamic routes you export getStaticPaths to declare which pages to build, or use SSR to render on demand.

Content collections

Content collections are Astro’s answer to structured content. You define a schema, and every markdown or JSON entry is validated at build time.

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

const blog = defineCollection({
  schema: z.object({
    title: z.string(),
    publishedAt: z.coerce.date(),
    tags: z.array(z.string()).default([]),
  }),
});

export const collections = { blog };
---
// src/pages/blog/index.astro
import { getCollection } from "astro:content";

const posts = (await getCollection("blog"))
  .sort((a, b) => b.data.publishedAt.valueOf() - a.data.publishedAt.valueOf());
---

<ul>
  {posts.map((post) => (
    <li>
      <a href={`/blog/${post.id}`}>{post.data.title}</a>
    </li>
  ))}
</ul>

You get type safety, validation and a clean way to query content without a database. For a site with hundreds of posts or docs pages, this is a significant quality-of-life improvement.

The islands architecture

This is Astro’s defining feature. A page is static HTML, and any interactive components are islands that hydrate independently. You control when each island loads with a client directive.

---
import Counter from "../components/Counter.jsx";
import Comments from "../components/Comments.jsx";
---

<Counter client:load />
<Comments client:visible />

The directives are:

  • client:load hydrates immediately — for critical, above-the-fold interactivity.
  • client:idle hydrates once the browser is free.
  • client:visible hydrates when the component scrolls into view.
  • client:only skips server rendering and renders only in the browser.
  • client:media hydrates when a media query matches.

Choosing the least eager directive that still works keeps the page fast. Most content pages need none at all.

Bring your own UI library

Astro can render components from React, Vue, Svelte, Solid and Preact — in the same project.

---
import ReactChart from "../components/Chart.jsx";
import VueForm from "../components/Form.vue";
---

<ReactChart client:load data={data} />
<VueForm client:visible />

Each framework is an optional integration. You can use React for one island and Svelte for another, or skip frameworks entirely. That flexibility means you are never locked in, and you can adopt a library only where it earns its place.

SSR and adapters

Astro is static by default, but it can render on demand. Add an adapter for your platform and opt routes into server rendering.

// astro.config.mjs
import { defineConfig } from "astro/config";
import node from "@astrojs/node";

export default defineConfig({
  output: "server",
  adapter: node({ mode: "standalone" }),
});

API endpoints live in src/pages/api and export HTTP methods, and server islands let you render dynamic fragments inside otherwise static pages. This makes Astro suitable for hybrid sites: a static blog with a dynamic dashboard, or a marketing site with personalised sections.

Best practices

  • Default to .astro components; add framework components only for interactivity.
  • Use the least eager client directive that works.
  • Keep content in collections with a schema instead of free-form markdown.
  • Scope styles with component <style> blocks or a chosen CSS tool.
  • Render static pages at build time and reserve SSR for dynamic routes.
  • Use layouts for shared shells and metadata.
  • Optimise images with the built-in image components.

Common mistakes

  • Adding client:load everywhere and losing the zero-JavaScript benefit.
  • Reaching for a UI framework when an .astro component would do.
  • Forgetting to define a schema and losing type safety in collections.
  • Assuming Astro cannot do dynamic apps and overlooking SSR.
  • Mixing too many UI frameworks and bloating the project for no reason.
  • Using client-side fetching for content that could be rendered at build time.

Where to go next

Astro is the pragmatic choice for content-heavy sites that still need pockets of interactivity. Solidify your HTML and CSS, then compare the model with Next.js, Nuxt and SvelteKit. Then build a small blog with a content collection and one interactive island to see the trade-offs for yourself.

Hydrating components

Only hydrate when the component is actually needed. client:visible waits until it scrolls into view, which keeps the initial load light.

Prefer
<Comments client:visible />
Avoid
<Comments client:load />

Static content

An .astro component renders to HTML with no client JavaScript. Reach for a framework component only when you need interactivity.

Prefer
---
const posts = await getCollection("blog");
---
<ul>
  {posts.map((p) => (
    <li><a href={p.id}>{p.data.title}</a></li>
  ))}
</ul>
Avoid
// a React component that fetches
// and renders static content,
// hydrating the whole list

FAQ

Frequently asked questions

Keep learning

Related topics from the roadmap.

$ start learning

Ready to start learning Astro?

Our interactive tutorial walks you through Astro step by step — with quizzes and real code you can run in the browser.