Skip to content
Astro as getting-started 4 min read

What is Astro?

Astro is a modern, server-first web framework built for content-rich websites — blogs, marketing sites, documentation, e-commerce storefronts, and portfolios. Its defining idea is simple but radical: render everything to HTML on the server, and ship zero JavaScript to the browser by default. When you do need interactivity, you opt in to it island by island, so visitors download only the code that a given component actually requires. The result is sites that are fast by construction rather than fast after a long optimization battle.

A server-first, content-focused framework

Most modern frameworks are application-first: they assume your page is a single-page app and hydrate the entire tree with JavaScript on load. Astro inverts that. It treats HTML as the deliverable and JavaScript as an enhancement you reach for deliberately.

This makes Astro especially strong for content-driven sites where most of the page is static text, images, and markup — content that has no reason to run JavaScript in the browser. You still get a great authoring experience: components, layouts, scoped styles, TypeScript, and a fast dev server with hot module reloading.

Astro is also UI-agnostic. You can author components in Astro’s own .astro syntax, or drop in React, Vue, Svelte, Solid, Preact, or Lit components — even mixing several frameworks on the same page.

The .astro component

An Astro component has two parts: a component script fenced between --- (the “code fence” or frontmatter), and a template below it. The script runs only on the server at build or request time — never in the browser.

---
// src/components/Greeting.astro
const { name } = Astro.props;
const now = new Date().toLocaleDateString("en-US");
---
<section class="card">
  <h2>Hello, {name}!</h2>
  <p>Rendered on {now}.</p>
</section>

<style>
  .card { padding: 1rem; border: 1px solid #ddd; border-radius: 8px; }
</style>

The <style> block is automatically scoped to this component, so those rules never leak to other parts of the page. Because the script runs on the server, you can freely read files, query a database, or call an API directly — no client bundle bloat, no exposed secrets.

Zero JavaScript by default

When Astro renders the component above, the browser receives plain HTML and CSS. There is no runtime, no hydration, no framework payload. This is the single biggest reason Astro pages tend to score near-perfect on Core Web Vitals.

Tip: “Zero JS by default” does not mean “no JS allowed.” It means you pay for interactivity only where you ask for it — the opposite of frameworks that hydrate everything whether you need it or not.

Islands: interactivity where you need it

When part of a page truly needs to be interactive — a search box, a cart widget, an image carousel — you mark it as an island. Astro renders the island’s HTML on the server, then ships and hydrates only that component’s JavaScript using a client:* directive.

---
// src/pages/index.astro
import Counter from "../components/Counter.jsx";
import Greeting from "../components/Greeting.astro";
---
<Greeting name="Astro" />

<!-- Hydrates as soon as the page loads -->
<Counter client:load />

<!-- Hydrates only when scrolled into view -->
<Counter client:visible />

Each directive controls when hydration happens:

DirectiveHydrates whenBest for
client:loadImmediately on page loadCritical above-the-fold widgets
client:idleBrowser is idleLow-priority interactivity
client:visibleComponent scrolls into viewBelow-the-fold widgets
client:mediaA CSS media query matchesMobile-only or desktop-only UI
client:onlySkips SSR; renders client-side onlyComponents that need browser-only APIs

Content collections

For blogs and docs, Astro provides content collections: type-safe, schema-validated sets of Markdown, MDX, or data files. You define a schema once, and Astro validates frontmatter and gives you full TypeScript autocompletion.

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

const blog = defineCollection({
  loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
  schema: z.object({
    title: z.string(),
    publishDate: z.date(),
    tags: z.array(z.string()).default([]),
  }),
});

export const collections = { blog };

Querying a collection is then fully typed:

import { getCollection } from "astro:content";

const posts = await getCollection("blog");
const sorted = posts.sort(
  (a, b) => b.data.publishDate.valueOf() - a.data.publishDate.valueOf()
);
console.log(sorted.map((p) => p.data.title));

Output:

[ 'Shipping zero JS', 'Why we chose Astro', 'Hello world' ]

Integrations

Astro is extended through integrations added in astro.config.mjs. The CLI wires them up for you:

npx astro add react tailwind
// astro.config.mjs
import { defineConfig } from "astro/config";
import react from "@astrojs/react";
import tailwind from "@tailwindcss/vite";

export default defineConfig({
  integrations: [react()],
  vite: { plugins: [tailwind()] },
});

Official integrations cover UI frameworks, server adapters (Node, Vercel, Cloudflare, Netlify), sitemaps, MDX, and more.

Note: Astro supports both fully static output (SSG) and on-demand server rendering (SSR). Add a server adapter when you need request-time rendering, API endpoints, or personalization.

Best practices

  • Reach for .astro components for everything static; only introduce a UI-framework island when a feature genuinely needs client-side state.
  • Prefer client:visible or client:idle over client:load for non-critical widgets to keep the main thread free.
  • Model your Markdown/MDX content with content collections so frontmatter is validated and strongly typed.
  • Keep secrets and data access in the component script — it never reaches the browser.
  • Lean on scoped <style> blocks instead of global CSS to avoid cascade conflicts.
  • Start static and add a server adapter only when a route truly needs request-time logic.
Last updated June 14, 2026
Was this helpful?