Jesse A. Green / notebook

Astro — User Guide

Generated May 2026 from withastro/astro at 1058428

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

Astro is a web framework for building fast, content-driven websites. It ships zero JavaScript to the browser by default — a radical departure from the React-first era. Pages are rendered to static HTML at build time (or on the server), and interactive components hydrate independently as "islands" in an otherwise static sea. The result: blazing-fast pages with a developer experience that works with every major UI framework.

This guide covers everything you need to build with Astro — from your first project to advanced routing, content collections, server-side rendering, and beyond.

Note

Astro requires Node.js >= 22.12.0. Check your version with node --version before starting. Older Node versions will cause subtle build failures.


1. Quick Start

Get a project running in under two minutes.

npm create astro@latest

The interactive wizard asks for:

  • Project name — directory to create
  • Template — starter (blog, portfolio, minimal, etc.)
  • Install dependencies — yes/no
  • Initialize git — yes/no

Once scaffolded:

cd my-project
npm run dev

Open http://localhost:4321 in your browser. Your Astro site is live.

Tip

The default dev server port is 4321. If that port is in use, pass --port 3000 (or any free port) to astro dev. You can also set server.port in astro.config.mjs to make it permanent.

What you get out of the box

Feature Default
Dev server Vite-powered HMR on port 4321
Build output Static HTML in ./dist
Styling Scoped CSS per component
Markdown Built-in, with Shiki syntax highlighting
TypeScript Supported natively, no config needed
Images Automatic optimization via Sharp
Dev Toolbar Enabled (audit tools, component inspector)

Project structure (default)

my-project/
├── public/             Static assets (copied verbatim to dist/)
├── src/
│   ├── components/     Reusable .astro components
│   ├── layouts/        Page layout wrappers
│   ├── pages/          File-based routing (every file = a route)
│   └── content/        Content collections (blog posts, docs, etc.)
├── astro.config.mjs    Project configuration
├── package.json
└── tsconfig.json

Every file in src/pages/ becomes a URL. src/pages/index.astro → /. src/pages/about.astro → /about. No router config needed.


2. Installation

# npm
npm create astro@latest

# pnpm
pnpm create astro@latest

# yarn
yarn create astro

The create astro wizard handles everything. Pick a template, install deps, and you're ready.

Manual installation

If you prefer to wire things up yourself:

mkdir my-project && cd my-project
npm init -y
npm install astro

Add scripts to package.json:

{
  "scripts": {
    "dev": "astro dev",
    "build": "astro build",
    "preview": "astro preview"
  }
}

Create src/pages/index.astro:

---
// Frontmatter (server-side JS/TS)
const title = "Hello, Astro!";
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>{title}</title>
  </head>
  <body>
    <h1>{title}</h1>
    <p>Welcome to my Astro site.</p>
  </body>
</html>

Run npm run dev and visit http://localhost:4321.

Adding a UI framework

Astro works with React, Vue, Svelte, Preact, Solid.js, and Alpine.js. Use astro add to install and configure any of them automatically:

# React
npx astro add react

# Vue
npx astro add vue

# Svelte
npx astro add svelte

# Preact
npx astro add preact

# Solid.js
npx astro add solid-js

# Alpine.js
npx astro add alpinejs

astro add installs the package, updates astro.config.mjs, and configures TypeScript automatically. You can add multiple frameworks at once:

npx astro add react vue

Important

Each astro add command modifies astro.config.mjs. Review the changes after running it — especially if you already have a custom config — to ensure nothing was overwritten unexpectedly.

Adding adapters (for SSR)

To enable server-side rendering, install an adapter:

# Node.js (self-hosted)
npx astro add node

# Vercel
npx astro add vercel

# Netlify
npx astro add netlify

# Cloudflare (Pages / Workers)
npx astro add cloudflare

Then set output: "server" in your config (see the Configuration section).

Node.js version requirement

Astro requires Node.js 22.12.0 or higher. If you're using a version manager:

# nvm
nvm install 22
nvm use 22

# fnm
fnm install 22
fnm use 22

3. Configuration

Astro is configured via astro.config.mjs (or .ts, .cjs) at the project root.

// astro.config.mjs
import { defineConfig } from 'astro/config';

export default defineConfig({
  // your options here
});

defineConfig() provides full TypeScript IntelliSense — hover over any option in your editor for docs.

Tip

You can also use astro.config.ts for a fully-typed config file. TypeScript support is first-class and catches config errors at editor time, not build time.

Core options

Option Default Description
root . Project root directory
srcDir ./src Source directory
publicDir ./public Static assets directory
outDir ./dist Build output directory
cacheDir ./node_modules/.astro Build cache directory
base / Base path for deployment (e.g. /my-app)
trailingSlash ignore URL trailing slash policy
output static Rendering mode (static or server)
compressHTML true Minify HTML output

Server options

export default defineConfig({
  server: {
    host: false,    // Set true to expose on LAN, or a specific IP
    port: 4321,     // Dev server port
    open: false,    // Auto-open browser on dev start
  },
});

Pass --host to astro dev to expose your dev server on your local network (useful for testing on mobile devices).

Build options

export default defineConfig({
  build: {
    format: 'directory',       // 'file' | 'directory' | 'preserve'
    assets: '_astro',          // Output folder for hashed assets
    inlineStylesheets: 'auto', // 'always' | 'auto' | 'never'
    concurrency: 1,            // Parallel pages to build at once
    redirects: true,           // Emit redirect HTML files
    serverEntry: 'entry.mjs',  // Server entry point filename (SSR)
  },
});

Increasing build.concurrency can speed up large static sites — experiment with values like 4 or 8 on multi-core machines.

Output modes

// Static site generation (default) — pre-renders all pages at build time
export default defineConfig({
  output: 'static',
});

// Server-side rendering — requires an adapter
import node from '@astrojs/node';
export default defineConfig({
  output: 'server',
  adapter: node({ mode: 'standalone' }),
});

Warning

The output: "hybrid" mode was removed in Astro v6. If you're migrating from v5 and used hybrid, switch to output: "static" — it now covers the same use case. Individual pages can still opt into server rendering with export const prerender = false.

Trailing slash

export default defineConfig({
  trailingSlash: 'ignore',  // Accept both /about and /about/
  // trailingSlash: 'always', // Redirect to /about/
  // trailingSlash: 'never',  // Redirect to /about
});

Caution

If trailingSlash is set to always, API endpoints that don't end with / will return 404. Ensure your fetch calls and form actions use the trailing slash when this mode is active.

Integrations

import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';

export default defineConfig({
  integrations: [
    react(),
    mdx(),
    sitemap(),
  ],
  site: 'https://example.com', // Required for sitemap
});

Image options

export default defineConfig({
  image: {
    // Remote image domains to allow optimization
    remotePatterns: [
      { protocol: 'https', hostname: '**.example.com' },
    ],
    // Default image service (Sharp is the default)
    service: {
      entrypoint: 'astro/assets/services/sharp',
    },
  },
});

Security options

export default defineConfig({
  security: {
    checkOrigin: true,          // CSRF protection (enabled by default)
    actionBodySizeLimit: '1MB', // Max body size for Actions
    serverIslandBodySizeLimit: '1MB', // Max body for Server Islands
  },
});

Environment variable schema

import { defineConfig, envField } from 'astro/config';

export default defineConfig({
  env: {
    schema: {
      PUBLIC_API_URL: envField.string({ context: 'client', access: 'public' }),
      DATABASE_URL: envField.string({ context: 'server', access: 'secret' }),
      PORT: envField.number({ context: 'server', access: 'public', default: 3000 }),
    },
  },
});

Variables declared here are validated at build time — missing required values fail the build immediately.

i18n routing

export default defineConfig({
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'es', 'fr', 'de'],
    fallback: {
      es: 'en', // Fall back to English if Spanish page missing
    },
    routing: {
      prefixDefaultLocale: false, // /en/about → /about
    },
  },
});

Prefetching

export default defineConfig({
  prefetch: {
    prefetchAll: false,
    defaultStrategy: 'hover', // 'tap' | 'hover' | 'viewport' | 'load'
  },
});

Fonts

export default defineConfig({
  fonts: [
    {
      provider: 'google',
      name: 'Inter',
      weights: [400, 600, 700],
    },
  ],
});

Scoped style strategy

export default defineConfig({
  scopedStyleStrategy: 'attribute', // 'where' | 'class' | 'attribute'
});
  • attribute — adds a data-astro-* attribute (default, broadest browser support)
  • class — adds a generated class name
  • where — uses :where() (zero specificity impact)

See the Admin Guide for the complete configuration reference including all adapter-specific options, advanced Vite configuration, and integration authoring APIs.


4. Core Usage

The .astro component

Every Astro component is a .astro file with two sections separated by ---:

---
// Frontmatter: runs on the server (or at build time)
// Import components, fetch data, define variables
import Header from '../components/Header.astro';
const greeting = "Hello!";
const posts = await fetch('/api/posts').then(r => r.json());
---

<!-- Template: HTML with {expressions} -->
<html lang="en">
  <head>
    <title>{greeting}</title>
  </head>
  <body>
    <Header />
    <main>
      <h1>{greeting}</h1>
      <ul>
        {posts.map(post => (
          <li><a href={`/blog/${post.slug}`}>{post.title}</a></li>
        ))}
      </ul>
    </main>
  </body>
</html>

<style>
  /* Scoped to this component by default */
  h1 { color: rebeccapurple; }
</style>

The frontmatter runs once at build time (static) or on every request (server mode). No JavaScript ships to the browser from this section.

File-based routing

Every file in src/pages/ becomes a route:

File URL
src/pages/index.astro /
src/pages/about.astro /about
src/pages/blog/index.astro /blog
src/pages/blog/[slug].astro /blog/:slug
src/pages/[...slug].astro /* (catch-all)
src/pages/api/data.ts /api/data (endpoint)

Dynamic routes

For static generation, export getStaticPaths():

---
// src/pages/blog/[slug].astro
export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map(post => ({
    params: { slug: post.id },
    props: { post },
  }));
}

const { post } = Astro.props;
---

<article>
  <h1>{post.data.title}</h1>
  <div set:html={post.rendered.html} />
</article>

Astro calls getStaticPaths() at build time and generates one HTML page per returned entry.

Props and component communication

---
// src/components/Card.astro
interface Props {
  title: string;
  description: string;
  href?: string;
}
const { title, description, href = '#' } = Astro.props;
---

<div class="card">
  <h2><a href={href}>{title}</a></h2>
  <p>{description}</p>
</div>

Pass props like HTML attributes:

---
import Card from '../components/Card.astro';
---

<Card title="Getting Started" description="Learn the basics." href="/docs" />

Layouts

Layouts are .astro components that wrap page content using <slot />:

---
// src/layouts/Base.astro
interface Props {
  title: string;
}
const { title } = Astro.props;
---

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>{title}</title>
  </head>
  <body>
    <nav><!-- site nav --></nav>
    <main>
      <slot />  <!-- Page content goes here -->
    </main>
    <footer><!-- site footer --></footer>
  </body>
</html>

Use in a page:

---
import Base from '../layouts/Base.astro';
---

<Base title="About Us">
  <h1>About Us</h1>
  <p>We build cool things.</p>
</Base>

Named slots

---
// src/layouts/TwoColumn.astro
---

<div class="layout">
  <aside>
    <slot name="sidebar" />
  </aside>
  <main>
    <slot />
  </main>
</div>
<TwoColumn>
  <nav slot="sidebar">
    <a href="/docs">Docs</a>
  </nav>
  <h1>Main content here</h1>
</TwoColumn>

CSS and styling

Styles in <style> tags are scoped to the component by default:

<h1>This heading is blue</h1>
<p>This paragraph is unstyled</p>

<style>
  /* Only applies to h1 in THIS component */
  h1 {
    color: blue;
  }
</style>

Global styles — opt out of scoping:

<style is:global>
  /* Applies everywhere */
  body {
    margin: 0;
    font-family: system-ui;
  }
</style>

Inline styles — skip processing entirely:

<style is:inline>
  /* Injected verbatim, no bundling */
  @import url('https://fonts.googleapis.com/css2?family=Inter');
</style>

CSS variables from frontmatter:

---
const primaryColor = '#ff6b6b';
const fontSize = '1.5rem';
---

<h1 class="title">Hello</h1>

<style define:vars={{ primaryColor, fontSize }}>
  .title {
    color: var(--primaryColor);
    font-size: var(--fontSize);
  }
</style>

Import CSS files:

---
import '../styles/global.css';
---

Tailwind CSS:

npx astro add tailwind

JavaScript and TypeScript

Astro supports TypeScript natively — rename any .astro frontmatter or import files to .ts.

Client-side scripts run in the browser:

<button id="toggle">Toggle</button>

<script>
  // This runs in the browser
  const btn = document.getElementById('toggle');
  btn?.addEventListener('click', () => {
    document.body.classList.toggle('dark');
  });
</script>

Scripts are bundled and deduplicated. A <script> in a component used 50 times is only included once.

External script files:

<script src="/scripts/analytics.js" is:inline></script>

Markdown and MDX

Any .md file in src/pages/ becomes a page:

---
title: My Blog Post
date: 2026-01-15
---

# My Blog Post

This is markdown content with **bold**, _italic_, and `code`.

For .mdx files (Markdown + JSX components), install the MDX integration:

npx astro add mdx

Then in .mdx files:

---
title: Interactive Post
---

import Counter from '../components/Counter.jsx';

# Interactive Post

This post has a live component:

<Counter client:load />

And regular markdown continues here.

API endpoints

Create .ts (or .js) files in src/pages/ that export HTTP method handlers:

// src/pages/api/hello.ts
import type { APIRoute } from 'astro';

export const GET: APIRoute = ({ params, request }) => {
  return new Response(JSON.stringify({ message: 'Hello!' }), {
    headers: { 'Content-Type': 'application/json' },
  });
};

export const POST: APIRoute = async ({ request }) => {
  const body = await request.json();
  return new Response(JSON.stringify({ received: body }));
};

UI framework components (Islands)

Install a framework integration, then use components in .astro files:

---
import ReactCounter from '../components/Counter.jsx';
import VueWidget from '../components/Widget.vue';
import SvelteModal from '../components/Modal.svelte';
---

<!-- No client directive = server render only, zero JS -->
<ReactCounter initialCount={0} />

<!-- client:load = hydrate immediately -->
<ReactCounter client:load initialCount={0} />

<!-- client:idle = hydrate when browser is idle -->
<VueWidget client:idle />

<!-- client:visible = hydrate when scrolled into view -->
<SvelteModal client:visible />

<!-- client:media = hydrate when media query matches -->
<VueWidget client:media="(max-width: 768px)" />

<!-- client:only = skip SSR, render client-side only -->
<ReactCounter client:only="react" initialCount={0} />

Client directives reference

Directive When it hydrates Use case
client:load Immediately on page load Above-the-fold interactive elements
client:idle When browser is idle (requestIdleCallback) Non-critical widgets
client:visible When entering the viewport Below-the-fold components
client:media="(query)" When CSS media query matches Mobile-only or desktop-only UI
client:only="framework" Client-side only (no SSR) Browser-API-dependent components

client:idle accepts an optional timeout: client:idle={{ timeout: 500 }} (milliseconds).

Note

Omitting a client:* directive renders the component to static HTML at build time (or on the server for SSR). The component's JavaScript never ships to the browser. This is the default and the key to Astro's performance story.

Astro global object

In .astro frontmatter and components, Astro is available globally:

---
// Request info (SSR)
const url = Astro.url;               // URL object
const params = Astro.params;         // Dynamic route params
const props = Astro.props;           // Component props

// Redirect (SSR)
if (!loggedIn) {
  return Astro.redirect('/login');
}

// Cookies (SSR)
const theme = Astro.cookies.get('theme')?.value ?? 'light';
Astro.cookies.set('theme', 'dark', { maxAge: 86400 });

// Site info
const canonicalURL = new URL(Astro.url.pathname, Astro.site);

// Locals (set by middleware)
const user = Astro.locals.user;
---

5. Common Tasks

Task 1: Build a blog with content collections

Step 1: Define your collection schema in src/content/config.ts:

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

const blog = defineCollection({
  type: 'content', // .md / .mdx files
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.coerce.date(),
    author: z.string().default('Anonymous'),
    tags: z.array(z.string()).default([]),
    draft: z.boolean().default(false),
    heroImage: z.string().optional(),
  }),
});

export const collections = { blog };

Step 2: Create posts in src/content/blog/:

---
title: "My First Post"
description: "Getting started with Astro blogging."
pubDate: 2026-01-15
author: "Jane Doe"
tags: ["astro", "web"]
---

# My First Post

Welcome to my blog! This is written in Markdown...

Step 3: Generate TypeScript types:

npx astro sync

Step 4: List posts:

---
// src/pages/blog/index.astro
import { getCollection } from 'astro:content';

const posts = await getCollection('blog', ({ data }) => !data.draft);
const sorted = posts.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
---

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

Step 5: Render individual posts:

---
// src/pages/blog/[slug].astro
import { getCollection, render } from 'astro:content';

export async function getStaticPaths() {
  const posts = await getCollection('blog', ({ data }) => !data.draft);
  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>
  <time>{post.data.pubDate.toLocaleDateString()}</time>
  <Content />
</article>

Task 2: Add a contact form with Actions

Actions are type-safe server functions that handle form submissions and other mutations.

Step 1: Define an action in src/actions/index.ts:

import { defineAction } from 'astro:actions';
import { z } from 'astro:schema';

export const server = {
  contact: defineAction({
    accept: 'form',
    input: z.object({
      name: z.string().min(1, 'Name is required'),
      email: z.string().email('Invalid email'),
      message: z.string().min(10, 'Message must be at least 10 characters'),
    }),
    handler: async ({ name, email, message }) => {
      // Send email, save to DB, etc.
      await sendEmail({ to: '[email removed]', name, email, message });
      return { success: true };
    },
  }),
};

Step 2: Use the action in a form:

---
import { actions } from 'astro:actions';
const result = Astro.getActionResult(actions.contact);
---

{result?.error && (
  <p class="error">{result.error.message}</p>
)}
{result?.data?.success && (
  <p class="success">Message sent! We'll be in touch.</p>
)}

<form method="POST" action={actions.contact}>
  <label>
    Name
    <input type="text" name="name" required />
  </label>
  <label>
    Email
    <input type="email" name="email" required />
  </label>
  <label>
    Message
    <textarea name="message" rows="5" required></textarea>
  </label>
  <button type="submit">Send</button>
</form>

Tip

Actions work with both form submissions (accept: 'form') and JSON requests. For progressive enhancement, the form works without JavaScript because it uses a standard POST — the action enriches it with validation and type safety.

Task 3: Optimize images

Astro's built-in <Image> component automatically optimizes images:

---
import { Image, Picture } from 'astro:assets';
import heroImage from '../assets/hero.jpg';
---

<!-- Optimized image: resized, converted to WebP, lazy-loaded -->
<Image
  src={heroImage}
  alt="A scenic mountain landscape"
  width={1200}
  height={600}
/>

<!-- Responsive image with multiple formats -->
<Picture
  src={heroImage}
  alt="A scenic mountain landscape"
  widths={[400, 800, 1200]}
  formats={['avif', 'webp', 'jpeg']}
  sizes="(max-width: 800px) 100vw, 800px"
/>

For remote images, declare allowed domains first:

// astro.config.mjs
export default defineConfig({
  image: {
    remotePatterns: [
      { protocol: 'https', hostname: 'images.unsplash.com' },
      { protocol: 'https', hostname: '**.cloudinary.com' },
    ],
  },
});

Then use remote URLs:

<Image
  src="https://images.unsplash.com/photo-xyz?w=1200"
  alt="Remote image"
  width={1200}
  height={800}
  inferSize={true}
/>

Task 4: Add SSR with an adapter

Install an adapter:

npx astro add node

Set output mode:

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

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

Build and run:

npm run build
node dist/server/entry.mjs

Mix static and dynamic pages:

Even in server mode, pages can pre-render by opting in. Opt into on-demand rendering per page:

---
// This page renders on every request
export const prerender = false;

const user = await getUser(Astro.cookies.get('session')?.value);
if (!user) return Astro.redirect('/login');
---

<h1>Dashboard for {user.name}</h1>

Or in static mode, opt specific pages into server rendering:

---
export const prerender = false; // Works in static mode too
---

Task 5: Protect routes with middleware

// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';

export const onRequest = defineMiddleware(async (context, next) => {
  const { pathname } = context.url;

  // Protect /dashboard routes
  if (pathname.startsWith('/dashboard')) {
    const session = context.cookies.get('session')?.value;
    if (!session) {
      return context.redirect('/login');
    }

    // Attach user to locals for downstream components
    context.locals.user = await getUserFromSession(session);
  }

  return next();
});

Chain multiple middleware:

import { sequence } from 'astro:middleware';
import { auth } from './middleware/auth';
import { logger } from './middleware/logger';
import { i18n } from './middleware/i18n';

export const onRequest = sequence(logger, i18n, auth);

Task 6: Set up i18n routing

// astro.config.mjs
export default defineConfig({
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'es', 'fr'],
    routing: {
      prefixDefaultLocale: false, // English at /, Spanish at /es/
    },
    fallback: {
      es: 'en', // Spanish falls back to English if page missing
      fr: 'en',
    },
  },
});

Organize pages by locale:

src/pages/
├── index.astro          → /
├── about.astro          → /about
├── es/
│   ├── index.astro      → /es/
│   └── about.astro      → /es/about
└── fr/
    └── index.astro      → /fr/

Use i18n helpers:

---
import { getRelativeLocaleUrl } from 'astro:i18n';
const locale = Astro.currentLocale ?? 'en';
---

<nav>
  <a href={getRelativeLocaleUrl(locale, '/')}>Home</a>
  <a href={getRelativeLocaleUrl(locale, '/about')}>About</a>
</nav>

Task 7: Generate an RSS feed

npm install @astrojs/rss
// src/pages/rss.xml.ts
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
import type { APIContext } from 'astro';

export async function GET(context: APIContext) {
  const posts = await getCollection('blog');
  return rss({
    title: 'My Blog',
    description: 'A collection of posts about web development.',
    site: context.site!,
    items: posts.map(post => ({
      title: post.data.title,
      pubDate: post.data.pubDate,
      description: post.data.description,
      link: `/blog/${post.id}/`,
    })),
    customData: `<language>en-us</language>`,
  });
}

Task 8: Generate a sitemap

npx astro add sitemap

Set your site URL (required):

export default defineConfig({
  site: 'https://example.com',
  integrations: [sitemap()],
});

The sitemap is generated automatically at /sitemap-index.xml on each build.

Task 9: Use environment variables

Declare your schema in config:

// astro.config.mjs
import { defineConfig, envField } from 'astro/config';

export default defineConfig({
  env: {
    schema: {
      PUBLIC_API_URL: envField.string({
        context: 'client',
        access: 'public',
      }),
      SECRET_KEY: envField.string({
        context: 'server',
        access: 'secret',
      }),
      FEATURE_FLAGS: envField.boolean({
        context: 'server',
        access: 'public',
        default: false,
      }),
    },
  },
});

Use validated env vars:

// Server-side (frontmatter, API routes, middleware)
import { SECRET_KEY, FEATURE_FLAGS } from 'astro:env/server';

// Client-side (browser scripts only)
import { PUBLIC_API_URL } from 'astro:env/client';

Important

Only variables declared with context: 'client' are available in browser scripts. Never import astro:env/server in client-side code — it will throw at runtime. Missing required variables cause the build to fail immediately with a clear error.

Task 10: Add prefetching

Enable prefetching for faster page transitions:

export default defineConfig({
  prefetch: true, // Use default strategy (hover)
  // Or configure:
  prefetch: {
    prefetchAll: false,
    defaultStrategy: 'hover', // 'tap' | 'hover' | 'viewport' | 'load'
  },
});

Prefetch specific links:

<!-- Prefetch on hover (default when prefetch is enabled) -->
<a href="/about" data-astro-prefetch>About</a>

<!-- Override strategy per-link -->
<a href="/popular-page" data-astro-prefetch="viewport">Popular</a>
<a href="/slow-page" data-astro-prefetch="load">Heavy Page</a>

Task 11: Use Server Islands for deferred content

Server Islands let you defer slow server-rendered sections — the page shell loads instantly, then the island fetches separately.

---
import UserGreeting from '../components/UserGreeting.astro';
import ProductGrid from '../components/ProductGrid.astro';
---

<!-- Page loads instantly, these render separately -->
<UserGreeting server:defer>
  <!-- Fallback shown while loading -->
  <div slot="fallback">Loading personalized content...</div>
</UserGreeting>

<ProductGrid server:defer>
  <div slot="fallback" class="skeleton">Loading products...</div>
</ProductGrid>

Note

Server Islands require an adapter (SSR mode). The server:defer directive tells Astro to stream the page immediately and fetch the island's content in a separate request. This is ideal for personalized content — user greetings, cart counts — that would otherwise block the entire page.

Task 12: Set up sessions

Sessions require an adapter and an encryption key.

Generate a key:

npx astro create-key

Add the output to your .env:

ASTRO_KEY=<generated-key-here>

Use sessions in pages:

---
export const prerender = false;

const session = Astro.session;

// Read
const cart = await session?.get('cart') ?? [];

// Write
await session?.set('cart', [...cart, newItem]);

// Destroy
await session?.destroy();
---

Task 13: Configure redirects

Static redirects in config (no runtime overhead):

export default defineConfig({
  redirects: {
    '/old-blog': '/blog',
    '/old-post': {
      destination: '/blog/new-post',
      status: 301, // Permanent redirect
    },
    '/docs/[...slug]': '/reference/[...slug]', // Dynamic redirect
  },
});

Task 14: Add MDX with custom components

npx astro add mdx

Pass custom components to MDX content:

---
import { Content } from '../content/blog/my-post.mdx';
import CodeBlock from '../components/CodeBlock.astro';
import Callout from '../components/Callout.astro';
---

<Content
  components={{
    pre: CodeBlock,      // Replace <pre> with custom component
    blockquote: Callout, // Replace <blockquote>
  }}
/>

Task 15: Use Markdoc for structured content

npx astro add markdoc

Markdoc provides a more constrained, validatable authoring experience compared to MDX — ideal for docs sites and content teams that shouldn't have arbitrary JSX access. Define custom tags and nodes to control exactly what authors can embed.

Task 16: Load external data with the Content Layer

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

const products = defineCollection({
  type: 'content_layer',
  loader: async () => {
    const response = await fetch('https://api.example.com/products');
    const data = await response.json();
    return data.map((product: any) => ({
      id: product.slug,
      ...product,
    }));
  },
  schema: z.object({
    name: z.string(),
    price: z.number(),
    inStock: z.boolean(),
  }),
});

export const collections = { products };

Query it like any other collection:

---
import { getCollection } from 'astro:content';
const products = await getCollection('products', ({ data }) => data.inStock);
---

Task 17: Deploy to Vercel

npx astro add vercel
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel';

export default defineConfig({
  output: 'server',
  adapter: vercel(),
});

Push to GitHub and connect the repo in the Vercel dashboard. Set output: "server" only if you need SSR — static sites deploy to Vercel without any adapter.

Task 18: View current environment and debug info

# Print system info for bug reports
npx astro info

# Copy to clipboard
npx astro info --copy

# Verbose build (see what's happening)
npx astro build --verbose

# Open the official docs
npx astro docs

6. Troubleshooting

Dev server issues

Dev server won't start — port in use

# Use a different port
npx astro dev --port 3000

# Or find and kill the process on 4321 (on Unix)
npx kill-port 4321

TypeError: Cannot read properties of undefined (reading 'validateOptions') in assets/internal.js during pnpm run dev (issue #16309)

This error occurs in some pnpm environments. Try:

  1. Clear the Astro cache: rm -rf node_modules/.astro
  2. Reinstall dependencies: pnpm install --force
  3. If using a custom image service, verify its validateOptions export matches the expected signature.

Components not rendered on page refresh using dev server (issue #15925)

If some components don't appear after a hard refresh but show on navigation, this is a known dev-server issue. Workaround:

  1. Stop the dev server.
  2. Clear .astro cache: rm -rf node_modules/.astro
  3. Restart: npx astro dev

astro dev starts but browser shows blank page

  • Check the terminal for build errors — a syntax error in any imported file causes a blank output.
  • Open DevTools console for client-side errors.
  • Ensure src/pages/index.astro (or index.html) exists.

Dev server not reflecting file changes

Clear cache and restart:

rm -rf node_modules/.astro
npx astro dev

Build issues

Build fails on Netlify / Vercel (Vite build fail) (issue #16196)

If you hit a Vite build failure on CI/CD:

  1. Ensure your adapter is installed: npx astro add netlify or npx astro add vercel
  2. Verify output: "server" is set when using an adapter
  3. Check for missing environment variables in your deployment settings
  4. Try clearing the build cache in your CI platform's settings

CSS missing in production when using export const partial = true (issue #16296)

Pages with export const partial = true may lose their styles in production builds. Workaround: avoid using partial = true for pages that have associated CSS, or move styles to a global stylesheet.

Build error: hybrid output mode not found

The hybrid output mode was removed in Astro v6. Change your config:

// Before (v5)
export default defineConfig({ output: 'hybrid' });

// After (v6)
export default defineConfig({ output: 'static' });
// Individual pages opt into SSR with: export const prerender = false;

TypeScript errors in .astro files

Run diagnostics:

npx astro check

For watch mode during development:

npx astro check --watch

If you see "types not found" for content collections, regenerate them:

npx astro sync

Cannot find module 'astro:content' or astro:actions

These virtual modules require astro sync to be run first. The dev server runs it automatically, but CI environments may need:

npx astro sync && npx astro build

Styling issues

Vue <style scoped> breaks after ClientRouter navigation (issue #16334)

Vue scoped styles can lose their scoping after soft navigation in dev mode when using ClientRouter. Notes:

  • In production builds, the issue does not occur.
  • In dev mode, use npx astro dev and do a hard refresh (Ctrl+Shift+R) to reset scoping.
  • Alternatively, use Astro's own scoped styles instead of Vue's for affected components.

Vue components with :deep() styles not applied in dev (issue #15898)

:deep() combinators in Vue components may not apply during dev when ClientRouter is active. Workaround: move deep styles to a global stylesheet or into an Astro component's <style is:global>.

Styles not applying at all in a component

Check:

  1. Is the component being used with a client:only directive? If so, the component renders client-side only — Astro scoped styles won't reach it. Use the framework's own styling.
  2. Is specificity fighting you? Use scopedStyleStrategy: 'where' in config to reduce scoped style specificity to zero.
  3. Are you importing CSS from node_modules? Import it in a layout's <style is:global> or via Vite config.

Unable to load UnoCSS styles with @apply (issue #16373)

UnoCSS's @apply directive can fail to resolve in Astro's Vite pipeline. Workarounds:

  1. Use UnoCSS's uno.css import approach instead of @apply
  2. Ensure your vite config in astro.config.mjs includes UnoCSS as a Vite plugin (not just a PostCSS plugin)
  3. Check UnoCSS docs for Astro-specific integration notes

Routing issues

404 on all pages except index

Ensure your hosting platform serves Astro's output correctly. For the Node adapter in standalone mode, run node dist/server/entry.mjs. For static sites, the dist/ folder should be the webroot — configure your server to serve index.html for directory paths.

trailingSlash: always causes 404 on endpoints (issue #16338)

When trailingSlash is always, API endpoints like /api/data become /api/data/ — update your fetch calls to match:

// This will 404 with trailingSlash: 'always'
const res = await fetch('/api/data');

// Use trailing slash
const res = await fetch('/api/data/');

Or switch to trailingSlash: 'ignore' if you don't need the redirect behavior.

Dynamic route params not working

Ensure the filename matches the param name:

  • [slug].astro → Astro.params.slug
  • [...slug].astro → Astro.params.slug (captures entire path segment)

For static sites, ensure getStaticPaths() returns all possible values.

Redirect loops

If middleware redirects unconditionally, it will loop. Always add a condition:

export const onRequest = defineMiddleware(async (context, next) => {
  const { pathname } = context.url;
  // Exclude the login page from auth check
  if (pathname.startsWith('/dashboard') && pathname !== '/login') {
    // check auth...
  }
  return next();
});

Content collections issues

getCollection() returns entries in different order than expected (issue #16306)

In Astro v6, getCollection() returns entries in filesystem order, which may differ from v5's behavior. Sort explicitly:

const posts = await getCollection('blog');
const sorted = posts.sort((a, b) =>
  b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
);

Never rely on getCollection()'s default order for display purposes.

Content collection schema validation failing

Run astro sync after changing schemas. Errors appear during the sync step and identify exactly which files fail validation.

render() returns undefined for content items

The render() function (imported from astro:content) only works with type: 'content' collections (.md/.mdx files). For type: 'data' collections (.json/.yaml), use the data directly without rendering.

Image issues

Remote images failing — "hostname not allowed"

Add the domain to image.remotePatterns:

export default defineConfig({
  image: {
    remotePatterns: [
      { protocol: 'https', hostname: 'your-cdn.com' },
    ],
  },
});

Sharp not found / image optimization disabled

Install Sharp explicitly:

npm install sharp

On some platforms (Vercel, etc.), Sharp is bundled automatically. If you see warnings about the fallback image service being used, it means Sharp isn't installed in your environment.

<Image> component throwing TypeScript errors about width/height

Both width and height are required for remote images. For local images, they're inferred automatically from the imported asset. If you need to override, pass both explicitly.

Integration issues

decap-cms-app@^3.12 import error (issue #16368)

This CMS package uses CJS-only internals that conflict with Astro's ESM pipeline. Workaround:

// astro.config.mjs
export default defineConfig({
  vite: {
    ssr: {
      noExternal: ['decap-cms-app'],
    },
  },
});

If that doesn't resolve it, import Decap CMS in a client:only component so it never runs through Astro's server pipeline.

astro add fails with permission errors

Run with explicit package manager:

npm exec astro add react
# or
pnpm dlx astro add react

Integration modifies config incorrectly

After running astro add, always review the diff to astro.config.mjs. If something looks wrong, restore from git and add the integration manually following its README.

Markdown / MDX issues

Syntax highlighting not working

Astro uses Shiki by default. If highlighting is missing:

  1. Ensure your code blocks have a language tag (```javascript not just ```)
  2. Shiki supports hundreds of languages — check the Shiki language list if yours isn't highlighting
  3. To switch to Prism: npm install prism-themes and configure in astro.config.mjs under markdown.syntaxHighlight: 'prism'

Remark/rehype plugin not working

// astro.config.mjs
import remarkGfm from 'remark-gfm';
import rehypeSlug from 'rehype-slug';

export default defineConfig({
  markdown: {
    remarkPlugins: [remarkGfm],
    rehypePlugins: [rehypeSlug],
  },
});

Ensure plugins are in remarkPlugins (not rehypePlugins) and vice versa. Remark operates on the Markdown AST; Rehype operates on the HTML AST.

Actions issues

Action handler not called

Verify:

  1. The form action attribute uses the imported action reference, not a string path.
  2. The form method is POST.
  3. Actions require a server context — ensure you're not on a fully static page without export const prerender = false.

Zod validation errors not showing

Use Astro.getActionResult() to retrieve the result:

---
const result = Astro.getActionResult(actions.myAction);
---

{result?.error?.fieldErrors?.email && (
  <p class="error">{result.error.fieldErrors.email[0]}</p>
)}

Session issues

Sessions not persisting

  1. Ensure ASTRO_KEY is set in your environment (generated by astro create-key).
  2. Sessions require an adapter. Check that output: "server" is configured.
  3. Verify cookies are being sent — check browser DevTools → Application → Cookies.

astro create-key output format

The command outputs a key in the format needed for your .env file directly. Copy the entire output line.

Performance issues

Large build taking too long

Increase build concurrency:

export default defineConfig({
  build: {
    concurrency: 4, // or 8 on powerful machines
  },
});

Large JavaScript bundle sizes

Check which components have client:load — these ship JS to the browser. Consider switching to client:idle or client:visible for non-critical components. Use npx astro build --verbose to see what's being bundled.

Dev server slow to start

Large src/content/ directories with many files slow down the initial sync. Run astro sync once to cache types, then subsequent starts are faster.


7. FAQ

General

Q: What makes Astro different from Next.js or Nuxt?

Astro ships zero JavaScript by default. Next.js and Nuxt ship a full React/Vue runtime to every visitor, even for mostly-static pages. In Astro, JavaScript only ships when you explicitly request it via client:* directives. This makes Astro exceptionally fast for content sites — blogs, docs, marketing pages — while also being capable of building fully interactive apps.

Q: Is Astro only for static sites?

No. Astro supports full server-side rendering via adapters (@astrojs/node, @astrojs/vercel, @astrojs/netlify, @astrojs/cloudflare). Set output: "server" in your config and install an adapter. You can also mix static and dynamic pages in the same project.

Q: What is the "Islands Architecture"?

It's Astro's mental model for hydration. A page is a "sea" of static HTML. Interactive components are "islands" — isolated, independently hydrating units. You control exactly which components hydrate and when, using client:* directives. Islands don't share state or bundle with each other by default.

Q: Can I use React AND Vue in the same project?

Yes. Run npx astro add react vue and use both. Each framework's runtime is loaded independently. You can even nest a Vue component inside a React component and vice versa — though sharing state between them requires a framework-agnostic store (like nanostores).

Q: Does Astro support TypeScript?

Yes, natively. TypeScript works in .astro frontmatter, .ts files, and framework components. Run npx astro check to type-check your project, or use npx astro check --watch during development.

Q: What is the minimum Node.js version?

Node.js 22.12.0 or higher. Astro uses modern Node.js APIs and will not run on older versions.

Q: Is Astro production-ready?

Yes. Astro is used by large organizations for production documentation sites, marketing sites, e-commerce, and more. The framework has a stable v6 release with active maintenance.

Q: What happened to output: "hybrid"?

The hybrid mode was removed in Astro v6. Replace it with output: "static" — the current static mode supports per-page server rendering via export const prerender = false, which was previously the unique feature of hybrid mode.

Content Collections

Q: What is a content collection?

A content collection is a folder in src/content/ with a defined schema. Astro validates every file against the schema and generates TypeScript types automatically. You query collections with getCollection() and getEntry() from astro:content.

Q: What's the difference between type: 'content' and type: 'data'?

  • type: 'content' — for .md and .mdx files with frontmatter. You get a render() function to produce HTML.
  • type: 'data' — for .json and .yaml files. Pure structured data, no rendering.

Q: What is the Content Layer?

The Content Layer (type: 'content_layer') extends collections to load data from any external source — an API, a CMS, a database. You provide an async loader function; Astro caches the result between builds.

Q: Do I need to run astro sync manually?

The dev server runs astro sync automatically. For production builds and CI, add it to your build command: astro sync && astro build. It generates the TypeScript types that astro:content imports rely on.

Q: Can I use Zod for validation in content schemas?

Yes. Astro exports Zod from astro:content (import { z } from 'astro:content') — use the full Zod API for schemas. You can also import Zod directly from astro:schema in Actions.

Q: How do I query content by a tag or category?

Pass a filter function to getCollection():

const reactPosts = await getCollection('blog', ({ data }) =>
  data.tags.includes('react')
);

Q: Can I paginate content collection results?

Yes, use Astro's paginate() helper in getStaticPaths():

---
export async function getStaticPaths({ paginate }) {
  const posts = await getCollection('blog');
  return paginate(posts, { pageSize: 10 });
}
const { page } = Astro.props;
---

Components and Rendering

Q: What's the difference between .astro and framework components?

.astro components always render server-side (or at build time) — no JavaScript ships to the browser. Framework components (React, Vue, etc.) also render server-side by default, but you can add client:* directives to hydrate them. Use .astro components for layout, navigation, and anything that doesn't need interactivity. Use framework components when you need client-side state.

Q: Can I share state between Astro islands?

Not directly — each island hydrates independently. Use a framework-agnostic store like nanostores for cross-island state:

// store.js
import { atom } from 'nanostores';
export const cartCount = atom(0);
// CartCounter.jsx (React island)
import { useStore } from '@nanostores/react';
import { cartCount } from '../store';

export function CartCounter() {
  const count = useStore(cartCount);
  return <span>{count} items</span>;
}

Q: When should I use client:only?

Use client:only for components that use browser-only APIs (window, localStorage, navigator) and cannot run server-side at all. Examples: third-party map SDKs, browser-specific charts, components that depend on document. Always pass the framework name: client:only="react".

Q: Can .astro components use React hooks?

No. Hooks are a React concept and only work in React components. Astro's frontmatter is plain JavaScript/TypeScript that runs at build/request time.

Q: How does Astro handle component CSS specificity?

By default (scopedStyleStrategy: 'attribute'), Astro adds a data-astro-* attribute to scoped elements and styles target that attribute. This adds one attribute to the specificity chain. Use scopedStyleStrategy: 'where' for zero-specificity scoping (easier to override with global styles).

Q: Can I use CSS Modules in Astro?

Yes. Import .module.css files:

---
import styles from './Button.module.css';
---
<button class={styles.button}>Click</button>

Q: Can I use Tailwind with Astro?

Yes. Run npx astro add tailwind — it installs Tailwind and configures the integration automatically.

Routing

Q: How do dynamic routes work in static mode?

Export getStaticPaths() from any page with [param] in its filename. Astro calls this function at build time, collects all param values, and generates one page per entry. Any slug not returned by getStaticPaths() won't exist in the build.

Q: How do I create a catch-all route?

Name the file [...slug].astro. The slug captures everything: /docs/a/b/c → params.slug = 'a/b/c'.

Q: Can I redirect based on user data (e.g., authentication)?

Yes, in SSR mode. Use middleware to check cookies/headers and call context.redirect(). In static mode, client-side redirects are possible via JavaScript, but server-side redirects require SSR.

Q: How do I add a 404 page?

Create src/pages/404.astro. Astro automatically uses it for unmatched routes.

Q: Can I have both static and server-rendered pages?

Yes. In static mode, add export const prerender = false to pages that need server rendering. In server mode, add export const prerender = true to pages that can be pre-rendered.

Server-Side Rendering

Q: Which adapter should I use?

Platform Adapter
Self-hosted Node.js @astrojs/node
Vercel @astrojs/vercel
Netlify @astrojs/netlify
Cloudflare Pages/Workers @astrojs/cloudflare

Q: Can I access request headers in SSR?

Yes. In .astro frontmatter or API routes:

---
const authHeader = Astro.request.headers.get('Authorization');
---

Q: How do cookies work?

Use Astro.cookies in .astro components or context.cookies in middleware and API routes:

// Get a cookie
const theme = Astro.cookies.get('theme')?.value;

// Set a cookie
Astro.cookies.set('theme', 'dark', {
  maxAge: 60 * 60 * 24 * 365, // 1 year
  httpOnly: true,
  secure: true,
  sameSite: 'strict',
});

// Delete a cookie
Astro.cookies.delete('theme');

Middleware

Q: When does middleware run?

Middleware runs on every request before the page or endpoint handler. In SSR mode this is every HTTP request. In static mode, middleware doesn't run at build time — you need SSR or a hosting platform that supports edge middleware.

Q: How do I pass data from middleware to pages?

Use context.locals:

// middleware.ts
context.locals.user = await getUser(context.cookies.get('session')?.value);

// page.astro
const { user } = Astro.locals;

Declare types for locals in src/env.d.ts:

declare namespace App {
  interface Locals {
    user: { id: string; name: string } | null;
  }
}

Actions

Q: What are Actions for?

Actions are type-safe server functions for mutations — form submissions, data writes, API calls that change state. They're similar to server actions in Next.js or Remix's actions, but with Zod validation built in and full TypeScript inference.

Q: Do Actions require JavaScript in the browser?

No — with accept: 'form', actions work as standard HTML form POSTs and function without JavaScript. They progressively enhance to use fetch() when JavaScript is available.

Q: Can Actions call a database?

Yes. Actions run server-side. Import your database client, ORM, or any server-only module.

Images

Q: Do I need to use <Image> for all images?

No — the standard <img> tag works fine. Use <Image> when you want automatic optimization (format conversion to WebP/AVIF, resizing, lazy loading). For decorative or background images, <img> or CSS is fine.

Q: Can I optimize images from a CMS or CDN?

Yes. Add the hostname to image.remotePatterns and use <Image src="https://..." />. Astro will proxy the image through its optimization endpoint at /_image.

Q: Is Sharp required?

Sharp is the default optimizer and handles local images. Without it, Astro falls back to a passthrough service (no optimization). Install Sharp explicitly with npm install sharp.

Performance

Q: How do I measure my Astro site's performance?

Use Lighthouse (built into Chrome DevTools), WebPageTest, or PageSpeed Insights. Astro sites typically score 95-100 on Lighthouse out of the box for static content. Use the Dev Toolbar's audit feature during development to catch common issues early.

Q: What is the Dev Toolbar?

The Dev Toolbar is an overlay available during astro dev (enabled by default). It shows component boundaries, runs accessibility audits, flags performance issues, and can be extended by integrations. Disable it with devToolbar: { enabled: false } in config if it's distracting.

Q: Does Astro support view transitions?

Yes, via the <ClientRouter /> component. Add it to your layout to enable smooth page transitions with the View Transitions API. Note that Vue scoped styles and some CSS-in-JS patterns have known edge cases with ClientRouter in dev mode (see the Troubleshooting section).

Deployment

Q: How do I deploy a static Astro site?

Build with npm run build. Upload the dist/ folder to any static hosting:

  • Vercel / Netlify: connect your GitHub repo — auto-detects Astro
  • GitHub Pages: use the official GitHub Actions workflow from Astro docs
  • Cloudflare Pages: connect repo, set build command astro build, output dist
  • Any CDN/host: upload dist/ contents to your webroot

Q: What's astro preview for?

It serves the production build (dist/) locally so you can verify the build before deploying. It's not meant for production use — use a proper web server or CDN for that.

Q: Can I run Astro in Docker?

Yes. Use the Node adapter and build a container:

FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine AS runtime
WORKDIR /app
COPY --from=build /app/dist ./dist
EXPOSE 4321
CMD ["node", "dist/server/entry.mjs"]

Integrations and Ecosystem

Q: How do I find third-party integrations?

Visit astro.build/integrations — the official integration directory lists hundreds of community and official integrations.

Q: Can I use Vite plugins?

Yes. Pass them via vite.plugins in your config:

import { defineConfig } from 'astro/config';
import glsl from 'vite-plugin-glsl';

export default defineConfig({
  vite: {
    plugins: [glsl()],
  },
});

Q: What is @astrojs/check?

It's the package behind astro check — the TypeScript diagnostics tool for .astro files. It's included as a dependency when you install Astro. Run it standalone with npx astro-check for CI integration.

Q: What is Astro DB?

Astro DB (@astrojs/db) is a managed SQLite-based database service built for Astro. Install it with npx astro add db. It provides a schema-first, TypeScript-native data layer with a local development database and cloud sync. Run database commands via npx astro db.

CLI

Q: What does astro info print?

System info for bug reports: Astro version, adapter, integrations, Node.js version, package manager, and OS. Use --copy to copy it to your clipboard before filing an issue.

Q: What does astro preferences do?

Manages per-user preferences stored locally (not in the project). Includes settings like whether to show the Dev Toolbar notification dot.

Q: How do I disable telemetry?

npx astro telemetry disable

Telemetry is anonymous and covers usage metrics. Disable it at any time — it does not affect functionality.


Complete CLI Reference

Commands

Command Description
astro dev Start development server (Vite-powered, default port 4321)
astro build Production build
astro preview Preview production build locally
astro check Run TypeScript diagnostics on .astro files
astro sync Generate TypeScript types for content collections
astro add <integration> Add and configure an integration
astro info Print debug/system info for bug reports
astro docs Open docs.astro.build in your browser
astro preferences Manage local user preferences
astro telemetry Enable or disable anonymous telemetry
astro create-key Generate encryption key for sessions/server islands
astro db Run Astro DB commands

Global flags

Flag Default Description
--config <path> auto-detected Path to config file
--root <path> . Project root directory
--site <url> — Override site config value
--base <path> / Override base config value
--verbose false Enable debug-level logging
--silent false Suppress all output

astro dev flags

Flag Default Description
--port <number> 4321 Dev server port
--host [string|bool] false Expose on network (true = all interfaces)
--open [string|bool] false Open browser on start
--allowedHosts <hosts> — Comma-separated allowed hostnames

astro build flags

Flag Default Description
--outDir <path> ./dist Output directory
--force false Force rebuild, bypass cache
--verbose false Verbose output

astro check flags

Flag Description
--watch Re-run on file changes

astro info flags

Flag Description
--copy Copy output to clipboard

Official Integrations Reference

Framework Renderers

Package Install command Framework
@astrojs/react npx astro add react React 18+
@astrojs/vue npx astro add vue Vue 3
@astrojs/svelte npx astro add svelte Svelte 4/5
@astrojs/preact npx astro add preact Preact
@astrojs/solid-js npx astro add solid-js Solid.js
@astrojs/alpinejs npx astro add alpinejs Alpine.js

Adapters (SSR)

Package Install command Platform
@astrojs/node npx astro add node Node.js (self-hosted)
@astrojs/vercel npx astro add vercel Vercel
@astrojs/netlify npx astro add netlify Netlify
@astrojs/cloudflare npx astro add cloudflare Cloudflare Pages/Workers

Content

Package Install command Purpose
@astrojs/mdx npx astro add mdx MDX (Markdown + JSX)
@astrojs/markdoc npx astro add markdoc Markdoc (structured content)

Utilities

Package Install command Purpose
@astrojs/sitemap npx astro add sitemap Auto-generate XML sitemap
@astrojs/partytown npx astro add partytown Off-main-thread third-party scripts
@astrojs/db npx astro add db Astro DB (managed SQLite)
@astrojs/rss npm install @astrojs/rss RSS/Atom feed generation
@astrojs/check bundled TypeScript diagnostics

See the Admin Guide for architecture internals, adapter configuration deep-dives, build pipeline details, security hardening, and the complete configuration schema.