Skip to main content

Learn Astro.js - Complete Beginner's Guide

Por Ramon Nuila 2025-01-15 Β· 25 min de lectura

Master Astro.js from the ground up. Learn why Astro is the fastest framework for content-driven websites and how to build lightning-fast web applications.

πŸš€ Learn Astro.js - The Complete Guide

Welcome to the complete guide to Astro.js! Whether you’re a seasoned developer or just starting your web development journey, this guide will teach you everything you need to know to build blazing-fast websites with Astro.


πŸ“– Table of Contents

  1. What is Astro?
  2. Why Choose Astro?
  3. Prerequisites
  4. Getting Started
  5. Project Structure
  6. Core Concepts
  7. Working with Components
  8. Pages and Routing
  9. Layouts
  10. Data Fetching
  11. Content Collections
  12. Islands Architecture
  13. Integrations
  14. Styling
  15. Deployment
  16. Best Practices

🌟 What is Astro?

Astro is a modern web framework for building fast, content-focused websites. It’s designed from the ground up to deliver better performance by shipping less JavaScript to the browser.

Key Philosophy

Astro follows a simple but powerful philosophy:

  • Content-first: Built specifically for content-heavy sites (blogs, marketing sites, documentation, e-commerce)
  • Server-first: HTML is rendered on the server, not in the browser
  • Zero JS by default: Only ship JavaScript when you actually need it
  • Framework agnostic: Use React, Vue, Svelte, or any other frameworkβ€”all in the same project

What Makes Astro Different?

Unlike traditional JavaScript frameworks (React, Vue, Next.js), Astro:

  1. Ships zero JavaScript by default - Your site loads instantly
  2. Allows framework mixing - Use React for one component, Vue for another
  3. Generates static HTML - Pre-renders pages at build time
  4. Enables partial hydration - Only interactive components load JavaScript (Islands Architecture)

Think of Astro as the β€œbest of all worlds”: the performance of static sites + the flexibility of dynamic frameworks.


⚑ Why Choose Astro?

Performance Benefits

Astro sites are incredibly fast because:

  • Zero JavaScript overhead: Most sites ship 40-90% less JavaScript
  • Automatic optimization: Images, fonts, and CSS are optimized out of the box
  • Partial hydration: Interactive components load independently
  • Edge-ready: Deploy to edge networks for sub-100ms response times

Real numbers:

  • Astro sites typically score 100/100 on Lighthouse
  • 2-3x faster load times compared to traditional frameworks
  • 10x less JavaScript shipped to browsers

Developer Experience

Why developers love Astro:

βœ… Familiar syntax: If you know HTML, CSS, and JavaScript, you know Astro βœ… Use any framework: React, Vue, Svelte, Solidβ€”or none at all βœ… TypeScript support: Built-in TypeScript without configuration βœ… Markdown & MDX: Write content in Markdown with component support βœ… Built-in features: Image optimization, RSS feeds, sitemaps included βœ… Great DX: Hot module replacement, helpful error messages, excellent docs

Perfect Use Cases

Astro excels at:

  • πŸ“ Blogs and content sites - Fast page loads, SEO-friendly
  • πŸ›οΈ E-commerce storefronts - Quick product pages, better conversions
  • πŸ“š Documentation sites - Clean, searchable, accessible
  • πŸ“± Marketing websites - High performance = better SEO = more leads
  • 🎨 Portfolio sites - Showcase work without bloated JavaScript

Not ideal for:

  • Highly interactive SPAs (Single Page Applications)
  • Real-time dashboards with constant data updates
  • Apps requiring persistent client-side state across many pages

🧰 Prerequisites

Before starting with Astro, you should have:

Required Knowledge

  • HTML & CSS basics: Understanding of web fundamentals
  • JavaScript fundamentals: Variables, functions, arrays, objects
  • Terminal/Command line: Basic navigation and commands
  • Package managers: Familiarity with npm or pnpm

Required Software

  • Node.js: Version 18.14.1 or higher (Download here)
  • Package manager: npm (included with Node.js), pnpm, or yarn
  • Code editor: Visual Studio Code recommended
  • Git (optional but recommended): For version control
  • Astro VS Code Extension: Syntax highlighting and IntelliSense
  • Browser DevTools: Chrome or Firefox developer tools
  • Terminal: iTerm2 (Mac), Windows Terminal, or integrated terminal in VS Code

Optional Knowledge (Helpful but not required)

  • TypeScript basics
  • React, Vue, or Svelte (if you want to use them)
  • Static site generation concepts
  • Git and GitHub

Don’t worry if you don’t know everything! Astro is beginner-friendly and you’ll learn as you go.


πŸ› οΈ Getting Started

Installation

Create a new Astro project in seconds:

# Using npm
npm create astro@latest

# Using pnpm (faster)
pnpm create astro@latest

# Using yarn
yarn create astro

Setup Wizard

The CLI will guide you through setup:

 astro   Launch sequence initiated.

   dir   Where should we create your new project?
         ./my-astro-site

  tmpl   How would you like to start your new project?
         ● Use blog template
         β—‹ Empty
         β—‹ Include sample files

    ts   Do you plan to write TypeScript?
         ● Yes  β—‹ No

   use   How strict should TypeScript be?
         ● Strict
         β—‹ Strictest
         β—‹ Relaxed

  deps   Install dependencies?
         ● Yes  β—‹ No

   git   Initialize a new git repository?
         ● Yes  β—‹ No

Pro tip: Choose β€œInclude sample files” for your first project to see examples.

Start Development Server

cd my-astro-site
npm install
npm run dev

Your site is now running at http://localhost:4321 πŸŽ‰

Available Commands

npm run dev          # Start dev server at localhost:4321
npm run build        # Build production site to ./dist/
npm run preview      # Preview built site locally
npm run astro --     # Run Astro CLI commands

πŸ“ Project Structure

Understanding the file structure is key to working efficiently with Astro:

my-astro-site/
β”œβ”€β”€ public/               # Static assets (images, fonts, etc.)
β”‚   └── favicon.svg
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ components/       # Reusable UI components
β”‚   β”‚   └── Header.astro
β”‚   β”œβ”€β”€ layouts/          # Page layouts
β”‚   β”‚   └── BaseLayout.astro
β”‚   β”œβ”€β”€ pages/            # File-based routing (becomes URLs)
β”‚   β”‚   β”œβ”€β”€ index.astro   # Homepage (/)
β”‚   β”‚   β”œβ”€β”€ about.astro   # About page (/about)
β”‚   β”‚   └── blog/
β”‚   β”‚       └── post-1.md # Blog post (/blog/post-1)
β”‚   β”œβ”€β”€ content/          # Content collections (Markdown/MDX)
β”‚   β”‚   └── blog/
β”‚   └── styles/           # CSS/SCSS files
β”‚       └── global.css
β”œβ”€β”€ astro.config.mjs      # Astro configuration
β”œβ”€β”€ package.json          # Project dependencies
└── tsconfig.json         # TypeScript configuration

Key Directories Explained

public/

  • Static files served as-is
  • Not processed by Astro
  • Use for images, fonts, robots.txt, etc.
  • Access via /filename.ext (e.g., /logo.webp)

src/pages/

  • File-based routing: Each file becomes a URL
  • Supports .astro, .md, .mdx, .html, and framework files
  • index.astro = homepage (/)
  • Folders create URL paths

src/components/

  • Reusable UI components
  • Not automatically routed
  • Can be .astro or framework components (.jsx, .vue, .svelte)

src/layouts/

  • Template wrappers for pages
  • Define common structure (header, footer, meta tags)
  • Imported and used in pages

src/content/

  • Content collections (blog posts, products, docs)
  • Type-safe with schema validation
  • Optimized for Markdown/MDX content

🎯 Core Concepts

Astro Components

Astro components (.astro files) have a unique structure:

---
// Component Script (Runs at build time)
const pageTitle = "My Astro Page";
const items = ["Item 1", "Item 2", "Item 3"];

// Fetch data (runs on server)
const response = await fetch('https://api.example.com/data');
const data = await response.json();
---

<!-- Component Template (HTML) -->
<html>
  <head>
    <title>{pageTitle}</title>
  </head>
  <body>
    <h1>{pageTitle}</h1>
    <ul>
      {items.map(item => <li>{item}</li>)}
    </ul>
  </body>
</html>

Key features:

  1. Frontmatter fence (---): JavaScript/TypeScript that runs at build time
  2. Template: HTML with JSX-like syntax
  3. No client-side JavaScript by default: Everything runs on the server

Component Props

Pass data to components like React:

---
// src/components/Greeting.astro
const { name, age } = Astro.props;
---

<div>
  <h1>Hello, {name}!</h1>
  <p>You are {age} years old.</p>
</div>

Use it:

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

<Greeting name="John" age={30} />

Astro.props and TypeScript

Type-safe props with TypeScript:

---
// src/components/Card.astro
interface Props {
  title: string;
  description?: string;
  image: string;
}

const { title, description, image } = Astro.props;
---

<div class="card">
  <img src={image} alt={title} />
  <h2>{title}</h2>
  {description && <p>{description}</p>}
</div>

🧩 Working with Components

Creating Your First Component

1. Create a Header component:

---
// src/components/Header.astro
const navItems = [
  { href: '/', label: 'Home' },
  { href: '/about', label: 'About' },
  { href: '/blog', label: 'Blog' },
];
---

<header>
  <nav>
    <ul>
      {navItems.map(item => (
        <li><a href={item.href}>{item.label}</a></li>
      ))}
    </ul>
  </nav>
</header>

<style>
  header {
    background: #1e293b;
    padding: 1rem;
  }

  nav ul {
    display: flex;
    gap: 2rem;
    list-style: none;
  }

  nav a {
    color: white;
    text-decoration: none;
  }
</style>

2. Use it in a page:

---
// src/pages/index.astro
import Header from '../components/Header.astro';
---

<html>
  <head>
    <title>My Site</title>
  </head>
  <body>
    <Header />
    <main>
      <h1>Welcome!</h1>
    </main>
  </body>
</html>

Scoped Styles

Styles in Astro components are automatically scoped to that component:

<div class="card">
  <h2>Card Title</h2>
</div>

<style>
  /* Only applies to THIS component */
  .card {
    border: 1px solid #ccc;
    padding: 1rem;
  }
</style>

No CSS conflicts, no naming conventions needed!

Slots

Use slots to pass content to components:

---
// src/components/Card.astro
---

<div class="card">
  <slot /> <!-- Content goes here -->
</div>
<Card>
  <h2>My Card Title</h2>
  <p>Card content here!</p>
</Card>

Named slots:

---
// src/components/Layout.astro
---

<div>
  <header>
    <slot name="header" />
  </header>
  <main>
    <slot /> <!-- Default slot -->
  </main>
  <footer>
    <slot name="footer" />
  </footer>
</div>
<Layout>
  <h1 slot="header">Page Title</h1>
  <p>Main content</p>
  <p slot="footer">Β© 2025</p>
</Layout>

πŸ“„ Pages and Routing

File-Based Routing

Astro uses file-based routing - the file structure in src/pages/ determines your URLs:

src/pages/
β”œβ”€β”€ index.astro          β†’ /
β”œβ”€β”€ about.astro          β†’ /about
β”œβ”€β”€ contact.astro        β†’ /contact
β”œβ”€β”€ blog/
β”‚   β”œβ”€β”€ index.astro      β†’ /blog
β”‚   β”œβ”€β”€ post-1.astro     β†’ /blog/post-1
β”‚   └── post-2.md        β†’ /blog/post-2
└── products/
    └── [id].astro       β†’ /products/123 (dynamic)

Dynamic Routes

Create dynamic pages with [param] syntax:

---
// src/pages/products/[id].astro

export async function getStaticPaths() {
  return [
    { params: { id: '1' } },
    { params: { id: '2' } },
    { params: { id: '3' } },
  ];
}

const { id } = Astro.params;
---

<h1>Product {id}</h1>

With data:

---
export async function getStaticPaths() {
  const products = await fetch('https://api.example.com/products')
    .then(r => r.json());

  return products.map(product => ({
    params: { id: product.id },
    props: { product }
  }));
}

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

<h1>{product.name}</h1>
<p>{product.description}</p>
<p>${product.price}</p>

Markdown Pages

Markdown files in src/pages/ automatically become pages:

---
layout: ../../layouts/BlogLayout.astro
title: "My First Post"
author: "Ramon"
date: "2025-01-15"
---

# My First Blog Post

This is **Markdown** content that becomes an HTML page!

🎨 Layouts

Layouts wrap pages with common structure:

---
// src/layouts/BaseLayout.astro
const { title } = Astro.props;
---

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{title} - My Site</title>
  </head>
  <body>
    <header>
      <nav>
        <a href="/">Home</a>
        <a href="/blog">Blog</a>
      </nav>
    </header>

    <main>
      <slot /> <!-- Page content -->
    </main>

    <footer>
      <p>Β© 2025 My Site</p>
    </footer>
  </body>
</html>

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

  main {
    max-width: 1200px;
    margin: 0 auto;
    padding: 2rem;
  }
</style>

Use in pages:

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

<BaseLayout title="About">
  <h1>About Us</h1>
  <p>Welcome to our site!</p>
</BaseLayout>

πŸ”„ Data Fetching

Fetch at Build Time

Fetch data during the build (server-side):

---
// Runs at build time, not in the browser
const response = await fetch('https://api.github.com/users/withastro');
const user = await response.json();
---

<div>
  <h1>{user.name}</h1>
  <p>{user.bio}</p>
  <img src={user.avatar_url} alt={user.name} />
</div>

No API keys exposed, no client-side requests!

Environment Variables

Store secrets safely:

# .env
API_KEY=your_secret_key
PUBLIC_API_URL=https://api.example.com
---
// Private (server-only)
const apiKey = import.meta.env.API_KEY;

// Public (accessible in browser)
const apiUrl = import.meta.env.PUBLIC_API_URL;

const data = await fetch(`${apiUrl}/data`, {
  headers: { 'Authorization': `Bearer ${apiKey}` }
}).then(r => r.json());
---

<div>{data.message}</div>

Rule: Prefix with PUBLIC_ to expose to the browser, otherwise server-only.


πŸ“š Content Collections

Content Collections are Astro’s built-in way to manage Markdown/MDX content with type safety.

Setup

1. Define your collection schema:

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

const blogCollection = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    description: z.string(),
    author: z.string(),
    date: z.date(),
    tags: z.array(z.string()),
    image: z.string().optional(),
  }),
});

export const collections = {
  'blog': blogCollection,
};

2. Add content:

---
# src/content/blog/my-first-post.md
title: "Getting Started with Astro"
description: "Learn how to build fast websites with Astro"
author: "Ramon Nuila"
date: 2025-01-15
tags: ["astro", "web development", "javascript"]
---

# Getting Started with Astro

Content goes here...

3. Query and display:

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

const blogPosts = await getCollection('blog');
const sortedPosts = blogPosts.sort((a, b) =>
  b.data.date.getTime() - a.data.date.getTime()
);
---

<h1>Blog Posts</h1>
{sortedPosts.map(post => (
  <article>
    <h2><a href={`/blog/${post.slug}`}>{post.data.title}</a></h2>
    <p>{post.data.description}</p>
    <small>{post.data.date.toLocaleDateString()}</small>
  </article>
))}

4. Create dynamic pages:

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

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

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

<article>
  <h1>{post.data.title}</h1>
  <p>By {post.data.author} on {post.data.date.toLocaleDateString()}</p>
  <Content />
</article>

🏝️ Islands Architecture

Astro’s Islands Architecture is revolutionary: only interactive components load JavaScript.

The Problem with Traditional Frameworks

Traditional SPAs (React, Vue) ship all JavaScript to the browser, even for static content.

Astro’s solution: Ship zero JavaScript by default, hydrate only what needs interactivity.

Client Directives

Control when components load JavaScript:

---
import ReactCounter from '../components/ReactCounter.jsx';
---

<!-- Never loads JavaScript (static HTML only) -->
<ReactCounter />

<!-- Load immediately -->
<ReactCounter client:load />

<!-- Load when visible (lazy loading) -->
<ReactCounter client:visible />

<!-- Load when browser is idle -->
<ReactCounter client:idle />

<!-- Load on media query match -->
<ReactCounter client:media="(max-width: 768px)" />

<!-- Only render on client, not server -->
<ReactCounter client:only="react" />

Example: Interactive Component

React Counter (needs JavaScript):

// src/components/ReactCounter.jsx
import { useState } from 'react';

export default function ReactCounter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

Use with selective hydration:

---
import ReactCounter from '../components/ReactCounter.jsx';
---

<h1>My Page</h1>
<p>This is static content - no JavaScript needed.</p>

<!-- Only this component loads React -->
<ReactCounter client:visible />

<p>More static content below...</p>

Result: Only the counter loads JavaScript, the rest is pure HTML!


πŸ”Œ Integrations

Astro has official integrations for popular tools:

Add an Integration

# React
npx astro add react

# Vue
npx astro add vue

# Svelte
npx astro add svelte

# Tailwind CSS
npx astro add tailwind

# Partytown (third-party scripts)
npx astro add partytown

# Sitemap
npx astro add sitemap

Manual Configuration

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

export default defineConfig({
  integrations: [
    react(),
    tailwind(),
  ],
});
  • UI Frameworks: React, Vue, Svelte, Solid, Preact, Lit
  • CSS: Tailwind, UnoCSS
  • CMS: Strapi, Contentful, Sanity, WordPress
  • Deployment: Vercel, Netlify, Cloudflare Pages
  • SEO: Sitemap, RSS feeds
  • Analytics: Google Analytics, Plausible, Fathom

🎨 Styling

Scoped Styles (Default)

<button class="primary">Click me</button>

<style>
  /* Only applies to this component */
  .primary {
    background: blue;
    color: white;
  }
</style>

Global Styles

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

Import External CSS

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

Tailwind CSS

npx astro add tailwind
<button class="bg-blue-500 text-white px-4 py-2 rounded">
  Click me
</button>

CSS Modules

---
import styles from './styles.module.css';
---

<div class={styles.container}>
  <h1 class={styles.title}>Hello</h1>
</div>

πŸš€ Deployment

Build for Production

npm run build

This creates an optimized site in ./dist/ folder.

Deploy to Vercel

# Install Vercel CLI
npm i -g vercel

# Deploy
vercel

Or connect your GitHub repo to Vercel for automatic deployments.

Deploy to Netlify

Option 1: Netlify CLI

# Install
npm install netlify-cli -g

# Deploy
netlify deploy --prod

Option 2: Git integration

  1. Push code to GitHub
  2. Connect repo in Netlify dashboard
  3. Build command: npm run build
  4. Publish directory: dist

Deploy to Cloudflare Pages

# Install Wrangler
npm install -g wrangler

# Login
wrangler login

# Deploy
wrangler pages publish dist

Other Platforms

Astro works with any static hosting:

  • GitHub Pages
  • AWS S3 + CloudFront
  • Google Cloud Storage
  • Azure Static Web Apps
  • Railway
  • Render

βœ… Best Practices

1. Use Content Collections for Markdown

Instead of manually importing files, use Content Collections for type safety and better DX.

2. Optimize Images

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

<Image
  src={heroImage}
  alt="Hero"
  width={1200}
  height={600}
  format="webp"
/>

3. Minimize Client-Side JavaScript

Only use client:* directives when absolutely necessary.

4. Use TypeScript

Enable TypeScript for better autocomplete and fewer bugs:

npm create astro@latest -- --typescript strict
<a href="/about" data-astro-prefetch>About</a>

6. Environment Variables

Use .env files and prefix public variables with PUBLIC_.

7. Component Organization

src/
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ ui/           # Buttons, cards, etc.
β”‚   β”œβ”€β”€ layout/       # Header, footer, etc.
β”‚   └── features/     # Feature-specific components
β”œβ”€β”€ layouts/
└── pages/

8. Performance Checklist

  • βœ… Use Astro’s <Image> component
  • βœ… Lazy load images with loading="lazy"
  • βœ… Minimize client-side JavaScript
  • βœ… Use client:visible for below-the-fold components
  • βœ… Enable Cloudflare/Vercel edge caching
  • βœ… Compress images before adding to project
  • βœ… Use WebP/AVIF formats

πŸŽ“ Next Steps

Congratulations! You now have a solid foundation in Astro.js. Here’s what to explore next:

Continue Learning

  1. Build a project: Start with a blog or portfolio
  2. Explore integrations: Try React, Vue, or Svelte components
  3. Add a CMS: Connect to Contentful, Sanity, or Strapi
  4. Optimize SEO: Add meta tags, structured data, sitemap
  5. Deploy: Get your site live on Vercel or Netlify

Resources

Build Something Amazing

You now have everything you need to build lightning-fast websites with Astro. The best way to learn is by building!

Need help building your next project? Our web development team specializes in Astro.js and can help you create high-performance websites that convert.


Happy coding! πŸš€