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.
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
| Directive | Behavior |
|---|---|
client:load | Hydrate immediately on page load |
client:idle | Hydrate when browser is idle (requestIdleCallback) |
client:visible | Hydrate when component scrolls into view (IntersectionObserver) |
client:media | Hydrate when media query matches |
client:only | Skip 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.