Astro 5 Content Collections and View Transitions
Astro 5 content collections represent a paradigm shift in how developers build content-heavy websites. With the Content Layer API, type-safe schemas, and built-in view transitions, Astro 5 delivers the best developer experience for blogs, documentation sites, marketing pages, and any project where content is king. Zero JavaScript is shipped to the client by default, yet pages feel like a single-page application.
This guide covers everything from defining content schemas and querying collections to implementing smooth view transitions that rival SPA navigation. We build a complete blog with categories, related posts, and animated page transitions — all generating static HTML at build time.
Setting Up Content Collections
Content collections in Astro 5 use the Content Layer API, a complete rewrite from Astro 4. Collections are defined in a central config file with Zod schemas for type safety. Every content file is validated at build time, catching errors before they reach production.
// src/content.config.ts
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const blog = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: z.object({
title: z.string().max(70),
description: z.string().max(160),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
author: z.string().default('Team'),
category: z.enum([
'tutorials', 'guides', 'news', 'opinion', 'case-studies'
]),
tags: z.array(z.string()).max(5),
image: z.object({
src: z.string(),
alt: z.string(),
}),
draft: z.boolean().default(false),
featured: z.boolean().default(false),
readingTime: z.number().optional(),
}),
});
const authors = defineCollection({
loader: glob({ pattern: '**/*.json', base: './src/content/authors' }),
schema: z.object({
name: z.string(),
bio: z.string(),
avatar: z.string(),
social: z.object({
twitter: z.string().optional(),
github: z.string().optional(),
linkedin: z.string().optional(),
}),
}),
});
const docs = defineCollection({
loader: glob({ pattern: '**/*.mdx', base: './src/content/docs' }),
schema: z.object({
title: z.string(),
description: z.string(),
section: z.string(),
order: z.number(),
lastUpdated: z.coerce.date(),
}),
});
export const collections = { blog, authors, docs };Querying and Rendering Collections
Astro 5 provides a powerful query API for fetching and filtering collection entries. All queries are fully typed based on your schema definitions. Moreover, the API supports sorting, filtering, and pagination out of the box.
---
// src/pages/blog/[...page].astro
import { getCollection } from 'astro:content';
import BlogCard from '../../components/BlogCard.astro';
import Pagination from '../../components/Pagination.astro';
import BaseLayout from '../../layouts/BaseLayout.astro';
export async function getStaticPaths({ paginate }) {
const posts = await getCollection('blog', ({ data }) => {
return !data.draft;
});
const sorted = posts.sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
);
return paginate(sorted, { pageSize: 12 });
}
const { page } = Astro.props;
---
<BaseLayout title="Blog">
<section class="posts-grid">
{page.data.map((post) => (
<BlogCard post={post} transition:name={`post-${post.id}`} />
))}
</section>
<Pagination page={page} />
</BaseLayout>---
// src/pages/blog/[slug].astro
import { getCollection, render } from 'astro:content';
import PostLayout from '../../layouts/PostLayout.astro';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => ({
params: { slug: post.id },
props: { post },
}));
}
const { post } = Astro.props;
const { Content, headings } = await render(post);
// Get related posts by category
const allPosts = await getCollection('blog');
const related = allPosts
.filter(p => p.data.category === post.data.category && p.id !== post.id)
.sort(() => Math.random() - 0.5)
.slice(0, 3);
---
<PostLayout post={post} headings={headings}>
<Content />
<aside class="related-posts">
<h3>Related Articles</h3>
{related.map(r => (
<a href={`/blog/${r.id}`}>{r.data.title}</a>
))}
</aside>
</PostLayout>View Transitions for SPA-Like Navigation
Astro 5 provides built-in view transitions using the browser’s View Transitions API with a polyfill for unsupported browsers. Pages transition smoothly without JavaScript frameworks, making static sites feel like single-page applications.
---
// src/layouts/BaseLayout.astro
import { ViewTransitions } from 'astro:transitions';
import Navigation from '../components/Navigation.astro';
import Footer from '../components/Footer.astro';
const { title, description } = 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}</title>
<meta name="description" content={description} />
<ViewTransitions fallback="swap" />
</head>
<body>
<Navigation transition:persist />
<main transition:animate="slide">
<slot />
</main>
<Footer transition:persist />
</body>
</html>---
// src/components/BlogCard.astro — with named transitions
const { post } = Astro.props;
const { title, image, pubDate, category } = post.data;
---
<article class="blog-card">
<img
src={image.src}
alt={image.alt}
transition:name={`hero-${post.id}`}
loading="lazy"
/>
<div class="card-content">
<span class="category"
transition:name={`cat-${post.id}`}>
{category}
</span>
<h2 transition:name={`title-${post.id}`}>
<a href={`/blog/${post.id}`}>{title}</a>
</h2>
<time>{pubDate.toLocaleDateString()}</time>
</div>
</article>
<style>
.blog-card {
border-radius: 12px;
overflow: hidden;
transition: transform 0.2s;
}
.blog-card:hover {
transform: translateY(-4px);
}
</style>Advanced Content Layer Features
Additionally, Astro 5 supports custom loaders that fetch content from any source — CMS APIs, databases, or remote files. This makes it possible to use Astro as a frontend for headless CMS platforms.
// Custom loader for headless CMS
import { defineCollection } from 'astro:content';
const cmsArticles = defineCollection({
loader: async () => {
const res = await fetch('https://api.cms.example.com/articles');
const articles = await res.json();
return articles.map(article => ({
id: article.slug,
...article,
}));
},
schema: z.object({
title: z.string(),
body: z.string(),
author: z.string(),
publishedAt: z.coerce.date(),
}),
});When NOT to Use Astro 5
Astro excels at content-driven sites but is not the right choice for highly interactive applications. If your site is primarily a dashboard, real-time collaboration tool, or complex form wizard, frameworks like Next.js or SvelteKit with client-side state management are better suited. Furthermore, if your team is deeply invested in a single framework ecosystem (React, Vue, Svelte), Astro’s island architecture adds learning overhead.
Therefore, choose Astro when content is your primary concern and interactivity is secondary. For applications where most pages require significant client-side JavaScript, a full-stack framework with server components provides a more cohesive development experience.
Key Takeaways
Astro 5 content collections combine type-safe content management with zero-JS output and smooth view transitions, making it the premier choice for content-driven websites in 2026. The Content Layer API validates your content at build time, custom loaders connect to any data source, and view transitions make static sites feel instant. Start with the built-in file loader and add complexity only when needed.
Key Takeaways
- Start with a solid foundation and build incrementally based on your requirements
- Test thoroughly in staging before deploying to production environments
- Monitor performance metrics and iterate based on real-world data
- Follow security best practices and keep dependencies up to date
- Document architectural decisions for future team members
For more web development topics, explore our guide on Next.js server components and web performance optimization. The Astro content collections documentation and view transitions guide are the definitive references.
In conclusion, Astro 5 Content Collections is an essential topic for modern software development. By applying the patterns and practices covered in this guide, you can build more robust, scalable, and maintainable systems. Start with the fundamentals, iterate on your implementation, and continuously measure results to ensure you are getting the most value from these approaches.