O Oriz.in Static internet platform
tech

Building a JAMstack Website with Astro 6 in 2026

Learn to build fast, modern JAMstack websites with Astro 6 in 2026. Complete guide covering setup, islands architecture, content collections, and deployment.

19 May 2026 Updated 19 May 2026 15 min read
astrojamstackweb-developmentstatic-site2026

Astro has established itself as the leading framework for content-focused websites. By version 6, it has matured significantly with improved performance, better developer experience, and a robust ecosystem. If you are building a blog, documentation site, marketing website, or any content-heavy application in 2026, Astro should be at the top of your list.

This guide walks you through building a complete JAMstack website with Astro 6, from initial setup to production deployment.

What Is JAMstack?

JAMstack stands for JavaScript, APIs, and Markup. It is an architecture pattern where websites are pre-rendered at build time and served as static files, with dynamic functionality handled through client-side JavaScript and API calls.

Benefits of JAMstack

  • Performance: Pre-rendered HTML loads instantly
  • Security: No server to hack, no database to breach
  • Scalability: Static files scale infinitely on any CDN
  • Developer Experience: Modern tooling, fast builds, hot reload
  • Cost: Hosting is often free or very cheap on platforms like Cloudflare Pages

Why Astro?

Astro takes the JAMstack approach further with its unique “Islands Architecture.” Unlike traditional frameworks that ship JavaScript for the entire page, Astro ships zero JavaScript by default. Interactive components (islands) are hydrated independently, meaning only the JavaScript for interactive elements is sent to the browser.

Astro 6 Key Features

  • Zero JavaScript by Default: HTML-only components ship no JS
  • Islands Architecture: Interactive components hydrate independently
  • Multi-Framework Support: Use React, Vue, Svelte, Solid, or Preact components
  • Content Collections: Type-safe content management with Zod validation
  • Server-Side Rendering (SSR): Optional SSR for dynamic pages
  • Image Optimization: Built-in image optimization with responsive formats
  • View Transitions: Smooth page transitions with the View Transitions API
  • Dev Toolbar: In-browser development tools for debugging

Getting Started with Astro 6

Prerequisites

  • Node.js 18.17.1 or higher (20+ recommended)
  • npm, pnpm, or yarn
  • Basic knowledge of HTML, CSS, and JavaScript

Project Setup

# Create a new Astro project
npm create astro@latest my-website

# Navigate to project directory
cd my-website

# Install dependencies
npm install

# Start development server
npm run dev

The CLI will prompt you for preferences:

  • Include sample files? Yes (for learning)
  • Install dependencies? Yes
  • TypeScript? Strict (recommended)
  • Git repository? Yes

Project Structure

my-website/
├── src/
│   ├── components/     # Reusable UI components
│   ├── layouts/        # Page layout templates
│   ├── pages/          # File-based routing
│   ├── content/        # Content collections (blog posts, docs)
│   ├── styles/         # Global styles
│   └── content.config.ts # Content collection definitions
├── public/             # Static assets (favicon, robots.txt)
├── astro.config.mjs    # Astro configuration
├── package.json
└── tsconfig.json

Building Pages

File-Based Routing

Astro uses file-based routing. Every .astro file in src/pages/ becomes a route.

src/pages/index.astro      → /
src/pages/about.astro      → /about
src/pages/blog/index.astro → /blog
src/pages/blog/[slug].astro → /blog/:slug

Basic Astro Component

---
// This is the component frontmatter (runs at build time)
const title = "Welcome to My Website";
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>{title}</title>
  </head>
  <body>
    <h1>{title}</h1>
    <p>Built with Astro 6 in 2026.</p>
  </body>
</html>

Using Layouts

Layouts provide consistent page structure across your site.

<!-- src/layouts/BaseLayout.astro -->
---
interface Props {
  title: string;
  description?: string;
}

const { title, description = "A website built with Astro 6" } = Astro.props;
---

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="description" content={description} />
    <title>{title}</title>
  </head>
  <body>
    <header>
      <nav>
        <a href="/">Home</a>
        <a href="/about">About</a>
        <a href="/blog">Blog</a>
      </nav>
    </header>
    <main>
      <slot />
    </main>
    <footer>
      <p>Built with Astro 6</p>
    </footer>
  </body>
</html>

Using the layout:

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

<BaseLayout title="Home" description="Welcome to my Astro 6 website">
  <h1>Welcome to My Website</h1>
  <p>This is the home page content.</p>
</BaseLayout>

Content Collections

Content Collections are Astro most powerful feature for content-focused websites. They provide type-safe content management with automatic validation.

Defining Collections

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

const blog = defineCollection({
  type: "content",
  schema: z.object({
    title: z.string(),
    description: z.string(),
    author: z.string(),
    publishDate: z.coerce.date(),
    updatedDate: z.coerce.date().optional(),
    tags: z.array(z.string()),
    coverImage: z.string().optional(),
  }),
});

export const collections = { blog };

Adding Content

---
title: "Getting Started with Astro 6"
description: "A beginner guide to building websites with Astro 6"
author: "Chirag Singhal"
publishDate: 2026-05-19
tags: ["astro", "tutorial", "web-dev"]
---

# Getting Started with Astro 6

Astro 6 makes building content websites easier than ever...

Querying Content

---
import { getCollection } from "astro:content";
import BaseLayout from "../layouts/BaseLayout.astro";

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

<BaseLayout title="Blog">
  <h1>Blog Posts</h1>
  {posts.map((post) => (
    <article>
      <h2>
        <a href={`/blog/${post.slug}`}>{post.data.title}</a>
      </h2>
      <p>{post.data.description}</p>
      <time>{post.data.publishDate.toLocaleDateString()}</time>
    </article>
  ))}
</BaseLayout>

Dynamic Routes for Blog Posts

---
import { getCollection } from "astro:content";
import BaseLayout from "../../layouts/BaseLayout.astro";

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();
---

<BaseLayout title={post.data.title} description={post.data.description}>
  <article>
    <h1>{post.data.title}</h1>
    <time>{post.data.publishDate.toLocaleDateString()}</time>
    <Content />
  </article>
</BaseLayout>

Islands Architecture

Islands are interactive components within otherwise static pages. Astro only sends JavaScript for these islands.

Using a React Component as an Island

---
import Counter from "../components/Counter.tsx";
---

<Counter client:load />
// src/components/Counter.tsx
import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Client Directives

DirectiveBehavior
client:loadHydrate immediately on page load
client:idleHydrate when browser is idle (requestIdleCallback)
client:visibleHydrate when component scrolls into view (IntersectionObserver)
client:mediaHydrate when media query matches
client:onlySkip SSR, render only on client

Choose the appropriate directive based on when interactivity is needed. client:visible is often the best choice for components below the fold.

Styling in Astro

Scoped Styles

---
---

<style>
  .card {
    border: 1px solid #e2e8f0;
    border-radius: 8px;
    padding: 16px;
  }
  .card h2 {
    color: #1a202c;
  }
</style>

<div class="card">
  <h2>Scoped Styles</h2>
  <p>These styles only apply to this component.</p>
</div>

Global Styles

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

CSS Frameworks

Astro supports all major CSS frameworks:

  • Tailwind CSS: npx astro add tailwind
  • UnoCSS: npx astro add unocss
  • Panda CSS: npx astro add panda

Image Optimization

Astro provides built-in image optimization through the Image component.

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

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

The Image component automatically:

  • Converts to modern formats (WebP, AVIF)
  • Generates responsive sizes
  • Lazy loads images
  • Provides proper aspect ratios

SEO and Metadata

Using Astro SEO Component

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

const seo = {
  title: "My Page Title",
  description: "My page description for search engines",
  canonical: "https://example.com/page",
  openGraph: {
    type: "website",
    locale: "en_IN",
    url: "https://example.com/page",
    title: "My Page Title",
    description: "My page description",
    image: "/og-image.jpg",
  },
};
---

<BaseLayout {...seo}>
  <h1>My Page</h1>
</BaseLayout>

Sitemap and RSS

npm install @astrojs/sitemap @astrojs/rss
// astro.config.mjs
import { defineConfig } from "astro/config";
import sitemap from "@astrojs/sitemap";

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

Performance Optimization

Built-in Optimizations

  • Automatic code splitting: Only load what is needed
  • Asset optimization: CSS minification, JS bundling
  • Image optimization: WebP/AVIF conversion, lazy loading
  • Font optimization: Self-host fonts, preload critical fonts

Manual Optimizations

---
// Preload critical resources
---

<head>
  <link rel="preload" href="/fonts/inter-var.woff2" as="font" crossorigin />
  <link rel="preconnect" href="https://fonts.googleapis.com" />
</head>

View Transitions

---
// Enable view transitions in astro.config.mjs
---

<html:head>
  <meta name="view-transition" content="same-origin" />
</html:head>

Deployment

Cloudflare Pages

# Install Cloudflare adapter
npx astro add cloudflare

# Build
npm run build

# Deploy with Wrangler CLI
npx wrangler pages deploy dist

Vercel

npx astro add vercel
npm run build
# Push to Git and connect to Vercel

Netlify

npx astro add netlify
npm run build
# Push to Git and connect to Netlify

GitHub Pages

# Install static adapter
npx astro add static

# Configure astro.config.mjs
export default defineConfig({
  site: "https://username.github.io/repo-name",
  base: "/repo-name",
});

# Build and deploy
npm run build
# Push dist/ to gh-pages branch

Building a Real Project: Blog Website

Here is a complete minimal blog structure:

src/
├── content/
│   └── blog/
│       ├── first-post.md
│       └── second-post.md
├── layouts/
│   └── BaseLayout.astro
├── pages/
│   ├── index.astro
│   ├── about.astro
│   └── blog/
│       ├── index.astro
│       └── [slug].astro
├── components/
│   ├── Header.astro
│   ├── Footer.astro
│   └── PostCard.astro
└── content.config.ts

This structure scales from a simple blog to a complex content site with multiple collections, dynamic routes, and interactive islands.

API Endpoints in Astro

Astro supports API routes for server-side functionality when using SSR adapters.

// src/pages/api/posts.ts
export async function GET({ params, request }) {
  const posts = await getCollection("blog");
  return new Response(JSON.stringify(posts), {
    headers: { "Content-Type": "application/json" },
  });
}

export async function POST({ request }) {
  const body = await request.json();
  // Process the request
  return new Response(JSON.stringify({ success: true }), {
    headers: { "Content-Type": "application/json" },
  });
}

Middleware in Astro

Middleware runs before every request and is useful for authentication, logging, or modifying requests.

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

export const onRequest = defineMiddleware(async (context, next) => {
  const start = Date.now();
  const response = await next();
  const duration = Date.now() - start;

  console.log(`${context.url.pathname} took ${duration}ms`);

  // Add security headers
  response.headers.set("X-Frame-Options", "DENY");
  response.headers.set("X-Content-Type-Options", "nosniff");

  return response;
});

Markdown and MDX Support

Astro supports both Markdown and MDX out of the box. MDX allows you to use components inside your content.

MDX Configuration

npm install @astrojs/mdx
// astro.config.mjs
import mdx from "@astrojs/mdx";

export default defineConfig({
  integrations: [mdx()],
});

Using Components in MDX

---
title: "My MDX Post"
---

import { Callout } from "../components/Callout.astro";

# My MDX Post

This is regular markdown content.

<Callout type="info">
  This is a custom component embedded in markdown!
</Callout>

More markdown content here.

Pagination

Astro provides built-in pagination support for content collections.

---
import { getCollection } from "astro:content";
import BaseLayout from "../layouts/BaseLayout.astro";

export async function getStaticPaths({ paginate }) {
  const posts = await getCollection("blog");
  return paginate(posts, { pageSize: 10 });
}

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

<BaseLayout title={`Blog - Page ${page.currentPage}`}>
  {page.data.map((post) => (
    <article>
      <h2>{post.data.title}</h2>
      <p>{post.data.description}</p>
    </article>
  ))}

  <nav>
    {page.url.prev && <a href={page.url.prev}>Previous</a>}
    {page.url.next && <a href={page.url.next}>Next</a>}
  </nav>
</BaseLayout>

Environment Variables

Astro supports environment variables through .env files.

# .env
PUBLIC_API_URL=https://api.example.com
SECRET_KEY=your-secret-key
---
// Access in Astro files
const apiUrl = import.meta.env.PUBLIC_API_URL;

// PUBLIC_ prefix makes it available in client-side code
// Non-prefixed variables are only available server-side
---

Testing Your Astro Site

Basic Testing Setup

npm install -D vitest @testing-library/dom
// vitest.config.ts
import { defineConfig } from "vitest/config";
import { getViteConfig } from "astro/config";

export default getViteConfig({
  test: {
    globals: true,
  },
});

Accessibility Considerations

Astro makes it easy to build accessible websites:

  • Semantic HTML: Use proper HTML elements (<nav>, <main>, <article>)
  • ARIA attributes: Add aria-label, aria-describedby where needed
  • Keyboard navigation: Ensure all interactive elements are keyboard accessible
  • Color contrast: Use sufficient contrast ratios (4.5:1 for normal text)
  • Alt text: Always provide descriptive alt text for images
<nav aria-label="Main navigation">
  <ul>
    <li><a href="/" aria-current="page">Home</a></li>
    <li><a href="/about">About</a></li>
    <li><a href="/blog">Blog</a></li>
  </ul>
</nav>

Final Thoughts

Astro 6 represents the current state of the art for content-focused websites. Its zero-JavaScript-by-default approach, combined with the flexibility to add interactivity through islands, makes it ideal for blogs, documentation sites, marketing pages, and e-commerce storefronts.

The JAMstack architecture ensures your site is fast, secure, and cheap to host. Combined with modern tooling like content collections, image optimization, and view transitions, Astro 6 provides everything you need to build a production-grade website in 2026.

Start with the official Astro documentation and build incrementally. The learning curve is gentle, and the results are immediately visible in your Lighthouse scores.


Disclaimer: This article is for informational purposes only. Framework versions and APIs change over time. Always refer to the official Astro documentation for the most current information. Code examples are based on Astro 6 as of May 2026.