Astro Basics
Astro fundamentals including project structure, components, islands architecture, and templating syntax
You are an expert in Astro fundamentals for building fast, content-focused websites with Astro.
## Key Points
- Default to `.astro` components for static content; only reach for React/Vue/Svelte when you need client-side interactivity.
- Use `client:visible` or `client:idle` over `client:load` to defer hydration and improve initial page load.
- Keep frontmatter logic minimal; extract complex data fetching into utility functions under `src/lib/` or `src/utils/`.
- Use TypeScript for component props via the `Props` interface to catch errors at build time.
- Leverage scoped styles in `.astro` files instead of CSS-in-JS to avoid shipping extra JavaScript.
- **Forgetting client directives**: A React/Vue/Svelte component without a `client:*` directive renders as static HTML with no interactivity. This is intentional but surprises newcomers.
- **Using `client:only` without specifying the framework**: `client:only` requires the framework name (e.g., `client:only="react"`), otherwise Astro does not know which renderer to use.
- **Assuming frontmatter runs on the client**: All code in the `---` fence runs on the server. You cannot access `window`, `document`, or browser APIs there.
- **Importing npm packages that rely on browser globals**: Some packages reference `window` at import time. Use dynamic imports inside `<script>` tags or `client:only` components to work around this.
## Quick Example
```astro
<Layout title="Home">
<h1 slot="header">Welcome</h1>
<p>This goes into the default slot.</p>
<p slot="footer">Copyright 2026</p>
</Layout>
```
```astro
<style is:global>
body { margin: 0; }
</style>
```skilldb get astro-skills/Astro BasicsFull skill: 252 linesAstro Basics — Astro
You are an expert in Astro fundamentals for building fast, content-focused websites with Astro.
Overview
Astro is a web framework designed for content-rich websites that ships zero JavaScript by default. It uses an islands architecture where interactive components hydrate independently, keeping pages fast. Astro components use a .astro file format with a frontmatter script fence and an HTML-like template below it.
Core Concepts
Project Structure
A standard Astro project follows this layout:
src/
components/ # Astro and UI framework components
layouts/ # Page layout templates
pages/ # File-based routing (each file becomes a route)
content/ # Content collections (Markdown, MDX, JSON)
styles/ # Global stylesheets
public/ # Static assets served as-is
astro.config.mjs # Astro configuration
Astro Component Anatomy
Every .astro file has two parts separated by a code fence (---):
---
// Component Script (runs at build time on the server)
import Header from '../components/Header.astro';
const title = "My Page";
const items = await fetch('https://api.example.com/items').then(r => r.json());
---
<!-- Component Template (HTML output) -->
<html>
<head><title>{title}</title></head>
<body>
<Header />
<ul>
{items.map(item => <li>{item.name}</li>)}
</ul>
</body>
</html>
The frontmatter script runs only on the server (at build time in static mode, or on each request in SSR mode). It never ships to the client.
Islands Architecture
Astro's islands architecture lets you embed interactive UI framework components (React, Vue, Svelte, etc.) inside otherwise static HTML pages. Each island hydrates independently.
---
import StaticCard from '../components/Card.astro';
import ReactCounter from '../components/Counter.jsx';
---
<!-- This is static HTML, zero JS -->
<StaticCard title="Hello" />
<!-- This island hydrates on the client -->
<ReactCounter client:load />
Client Directives
Client directives control when and how islands hydrate:
| Directive | Behavior |
|---|---|
client:load | Hydrate immediately on page load |
client:idle | Hydrate once the browser is idle |
client:visible | Hydrate when the component scrolls into view |
client:media="(max-width: 768px)" | Hydrate when a CSS media query matches |
client:only="react" | Skip server rendering, only render on client |
Expressions and Templating
Astro templates support JavaScript expressions inside curly braces:
---
const visible = true;
const classes = ['card', 'featured'];
---
{visible && <p>Conditionally rendered</p>}
<div class:list={['base', { active: visible }, classes]}>
Dynamic classes
</div>
<ul>
{['A', 'B', 'C'].map(letter => <li>{letter}</li>)}
</ul>
Slots
Astro components accept children via slots, similar to web component slots:
---
// Layout.astro
const { title } = Astro.props;
---
<html>
<head><title>{title}</title></head>
<body>
<header><slot name="header" /></header>
<main><slot /></main>
<footer><slot name="footer" /></footer>
</body>
</html>
Usage:
<Layout title="Home">
<h1 slot="header">Welcome</h1>
<p>This goes into the default slot.</p>
<p slot="footer">Copyright 2026</p>
</Layout>
Props and TypeScript
---
interface Props {
title: string;
description?: string;
tags: string[];
}
const { title, description = 'Default description', tags } = Astro.props;
---
<article>
<h2>{title}</h2>
<p>{description}</p>
<ul>{tags.map(t => <li>{t}</li>)}</ul>
</article>
Implementation Patterns
Fetching Data at Build Time
---
// Runs at build time (static) or per-request (SSR)
const response = await fetch('https://api.example.com/posts');
const posts = await response.json();
---
<ul>
{posts.map(post => (
<li>
<a href={`/blog/${post.slug}`}>{post.title}</a>
</li>
))}
</ul>
Scoped Styles
Styles in .astro files are scoped to that component by default:
<style>
/* Only applies to <h1> elements in this component */
h1 {
color: navy;
font-size: 2rem;
}
</style>
<h1>Scoped heading</h1>
Use is:global to opt out of scoping:
<style is:global>
body { margin: 0; }
</style>
Script Tags
<!-- Bundled and deduplicated by Astro -->
<script>
document.querySelector('#btn').addEventListener('click', () => {
console.log('clicked');
});
</script>
<!-- Inline, not processed -->
<script is:inline>
console.log('runs exactly as written');
</script>
Core Philosophy
Astro exists to prove that most of the web does not need JavaScript. The framework embraces a content-first mentality where pages are static HTML by default, and interactivity is an opt-in exception rather than a baseline assumption. This inversion of the typical SPA model means your starting point is always zero client-side JavaScript, and you consciously add only what users need.
The islands architecture is not just a performance trick but a design philosophy. Each interactive component is an isolated, self-contained unit that hydrates independently. This forces you to think about interactivity as discrete, purposeful decisions rather than a blanket capability. The result is a site where the structure of the code mirrors the structure of the user experience: mostly content, with pockets of interaction.
Astro's component model leans into the web platform itself. The .astro file format uses a thin abstraction over HTML, CSS, and JavaScript rather than inventing a new paradigm. Scoped styles, slots, and JSX-like expressions map directly to web concepts. When you reach for a framework island, you are explicitly leaving Astro's lightweight layer and accepting the cost of a heavier runtime. This transparency about trade-offs is central to writing good Astro code.
Anti-Patterns
-
Wrapping entire pages in framework components. If you nest your whole page inside a React or Vue root just to use familiar tooling, you negate Astro's zero-JS default and turn it into a heavier, slower SPA with extra build complexity.
-
Reaching for
client:loadon every island. Eagerly hydrating all interactive components defeats the purpose of partial hydration. Evaluate whetherclient:visibleorclient:idlewould suffice, reservingclient:loadfor above-the-fold elements that require immediate interactivity. -
Using global state managers across islands. Importing Redux, Zustand, or a heavyweight store to share state between islands introduces tight coupling and large bundles. Use nanostores or browser-native mechanisms (events, localStorage) to keep islands truly independent.
-
Embedding heavy logic in frontmatter. The code fence is for data fetching and light preparation, not business logic. When frontmatter grows beyond a handful of statements, extract it into utility modules under
src/lib/so components stay readable and testable. -
Treating
.astrofiles like JSX components. Astro components are not React components. They have no lifecycle, no state, and no re-renders. Writing them as if they had client-side behavior (attaching event handlers inline, expecting reactivity) leads to confusion and bugs.
Best Practices
- Default to
.astrocomponents for static content; only reach for React/Vue/Svelte when you need client-side interactivity. - Use
client:visibleorclient:idleoverclient:loadto defer hydration and improve initial page load. - Keep frontmatter logic minimal; extract complex data fetching into utility functions under
src/lib/orsrc/utils/. - Use TypeScript for component props via the
Propsinterface to catch errors at build time. - Leverage scoped styles in
.astrofiles instead of CSS-in-JS to avoid shipping extra JavaScript.
Common Pitfalls
- Forgetting client directives: A React/Vue/Svelte component without a
client:*directive renders as static HTML with no interactivity. This is intentional but surprises newcomers. - Using
client:onlywithout specifying the framework:client:onlyrequires the framework name (e.g.,client:only="react"), otherwise Astro does not know which renderer to use. - Assuming frontmatter runs on the client: All code in the
---fence runs on the server. You cannot accesswindow,document, or browser APIs there. - Importing npm packages that rely on browser globals: Some packages reference
windowat import time. Use dynamic imports inside<script>tags orclient:onlycomponents to work around this. - Overusing islands: Not every interactive element needs a framework island. Small interactions (toggles, menus) can be handled with a vanilla
<script>tag, avoiding the cost of shipping a framework runtime.
Install this skill directly: skilldb add astro-skills
Related Skills
Astro Content Collections
Content collections in Astro for managing Markdown, MDX, JSON, and YAML content with type-safe schemas
Astro Deployment
Deploying Astro sites to Vercel, Netlify, Cloudflare Pages, and other platforms
Astro Integrations
Using React, Vue, Svelte, and other UI framework islands within Astro pages
Astro Middleware
Middleware patterns in Astro for authentication, request modification, response headers, and shared context
Astro Routing
File-based and dynamic routing in Astro including static paths, rest parameters, and route priority
Astro SSR
Server-side rendering in Astro with adapters for Node, Vercel, Netlify, Cloudflare, and Deno