Building My First Blog with Astro 5: A Modern Take on Static Site Generation
After hearing so much buzz about Astro, I finally decided to dive in and build my first blog with the recently released Astro 5. What started as a simple experiment turned into a fascinating exploration of modern web development patterns. Here's what I discovered.
Why Astro ?
I'd been using traditional frameworks like Next.js and Gatsby for static sites, but they always felt like overkill for content-heavy sites. These frameworks ship entire JavaScript bundles even for simple blogs, impacting performance and user experience.
Astro's promise of "zero JavaScript by default" caught my attention. The concept seemed revolutionary: build with familiar frameworks like React, Vue, or Svelte, but only ship JavaScript when absolutely necessary. This approach promises the best of both worlds - modern developer experience with optimal performance.
What really sold me was the component islands architecture. Instead of hydrating entire pages, Astro lets you selectively hydrate only the interactive components you need. This means a blog post with a single interactive element doesn't need to load React for the entire page.
The Tech Stack
For this project, I went with a cutting-edge stack to explore the latest in web development:
- Astro 5 - The latest version with improved performance, better TypeScript support, and enhanced content collections
- React 19 - For interactive components where needed, taking advantage of the new concurrent features
- TailwindCSS 4 - The new major version with Vite integration, eliminating the need for separate config files
- MDX - For rich content authoring, allowing React components directly in markdown
- TypeScript - For type safety throughout, with strict mode enabled for maximum reliability
- Pagefind - For client-side search functionality without external dependencies
- Satori - For automatic Open Graph image generation
This stack represents the current state of the art in static site generation, combining proven technologies with their latest innovations.
Key Features Implemented
1. Content Collections with Type Safety
One of Astro's standout features is its content collections system. Coming from manually managing markdown files, this was a game-changer.
I defined a schema for blog posts with Zod validation:
// src/content.config.ts
import { defineCollection, z } from 'astro:content';
export const collections = {
posts: defineCollection({
loader: { }, // Uses default glob loader
schema: z.object({
title: z.string(),
date: z.date(),
description: z.string().optional(),
tags: z.array(z.string()).optional(),
draft: z.boolean().optional()
})
})
};
This approach provides several benefits:
- Compile-time safety: TypeScript catches errors in frontmatter before build
- IntelliSense support: Full autocomplete when working with post data
- Automatic validation: Zod ensures all posts meet the schema requirements
- Type inference: Astro automatically generates types from the schema
The content collection system eliminated the need for a headless CMS while providing enterprise-level type safety and developer experience.
2. Dynamic Tag-Based Navigation
I implemented a sophisticated tag system that automatically generates navigation from post metadata. The system dynamically builds tag clouds and creates paginated tag pages:
---
// Extract unique tags from all posts
const tagSet = new Set<string>();
posts.forEach((p) => {
p.data.tags.forEach((t) => tagSet.add(t));
});
const tags = Array.from(tagSet).sort();
---
<section class="mb-8 flex flex-wrap gap-2">
{tags.map((tag) => (
<a
href={`/tags/tag/${encodeURIComponent(tag)}/`}
class="rounded bg-indigo-50 px-3 py-1 text-xs font-medium text-indigo-700 hover:bg-indigo-100"
>
#{tag}
</a>
))}
</section>
The tag system includes:
- Automatic tag extraction: Tags are collected from all posts automatically
- URL-safe encoding: Special characters and spaces are properly encoded
-
Dynamic routing: Each tag gets its own paginated page at
/tags/tag/[tag]/[page]
- Alphabetical sorting: Tags are sorted for consistent navigation
- Responsive design: Tag cloud adapts to different screen sizes
This approach scales automatically - adding new tags to posts immediately updates the navigation without any manual intervention.
3. Automatic OG Image Generation
One of the most impressive features I implemented was automatic Open Graph image generation using the astro-satori
integration. This creates beautiful social media preview images for every blog post:
satori({
satoriOptionsFactory: async () => {
const font = await fetch(
'https://og-playground.vercel.app/inter-latin-ext-700-normal.woff'
).then(r => r.arrayBuffer());
return {
width: 1200,
height: 630,
embedFont: true,
fonts: [{ name: 'Inter', data: font, weight: 700 }],
};
},
satoriElement: ({ title, description }) => ({
type: 'div',
props: {
style: {
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
padding: 60,
background: '#1e1b4b',
color: '#fff',
fontFamily: 'Inter',
},
children: [
{ type: 'h1', props: { style: { fontSize: 72, margin: 0 }, children: [title] } },
{ type: 'p', props: { style: { fontSize: 32, marginTop: 40 }, children: [description] } }
]
}
})
})
This integration provides:
- Automatic generation: OG images are created for every post during build
- Custom styling: Full control over the visual design using CSS-in-JS
- Font loading: Custom fonts are fetched and embedded for consistent branding
- Dynamic content: Each image includes the post title and description
- Optimal sizing: Images are generated at the perfect 1200x630 dimensions for social media
The result is professional-looking social media previews that significantly improve link sharing engagement without any manual design work.
4. Search Integration
I integrated Pagefind for fast, client-side search functionality without external dependencies or API calls:
pagefind({
serviceOptions: { forceLanguage: 'fr' },
ui: { resetStyles: false }
})
Pagefind brings several advantages:
- No external dependencies: Everything works offline after initial page load
- Instant search: Results appear as you type with no network latency
- Automatic indexing: Content is indexed during the build process
- Lightweight: Minimal impact on bundle size
- Customizable UI: Full control over search interface styling
- Multi-language support: Configured for French content with proper language processing
The search indexes all blog post content, titles, and descriptions, making it easy for visitors to find relevant content quickly. The fact that it works entirely client-side means no server costs or complexity.
What Surprised Me
1. TailwindCSS 4 Integration
The new Vite plugin approach in Tailwind 4 is incredibly smooth and represents a major improvement in developer experience. No more config files - everything just works through the Vite plugin:
// astro.config.mjs
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
vite: {
plugins: [tailwindcss()],
},
});
This eliminates the traditional tailwind.config.js
file and integrates directly with Vite's build process. The benefits include:
- Zero configuration: Works out of the box with sensible defaults
- Faster builds: Better integration with Vite's caching and hot reload
- Automatic purging: Unused styles are automatically removed
- Better IntelliSense: Improved autocomplete and validation in IDEs
2. Component Islands Architecture
Astro's island architecture is brilliant and fundamentally changes how I think about client-side JavaScript. I can write React components for interactive parts while keeping the rest static:
<!-- This runs at build time -->
<PostCard post={post} />
<!-- This ships JavaScript only when needed -->
<Counter client:load />
The key insight is that most content doesn't need JavaScript at all. With islands, I can:
- Selective hydration: Only interactive components get JavaScript
- Multiple frameworks: Mix React, Vue, Svelte components on the same page
-
Lazy loading: Components hydrate only when needed (
client:visible
,client:idle
) - Reduced bundle size: Non-interactive components compile to static HTML
3. Performance Out of the Box
Without doing any optimization, my blog loads incredibly fast. The default behavior of shipping zero JavaScript unless explicitly needed makes a huge difference.
The performance characteristics are remarkable:
- Minimal JavaScript: Only ships what's absolutely necessary
- Instant navigation: Server-side rendered pages load immediately
- Optimal caching: Static assets are cached efficiently
- Small bundle sizes: Each page only includes required JavaScript
Coming from Next.js where every page includes React by default, this was a revelation. The lighthouse scores consistently hit 100/100 without any performance tuning.
Challenges I Faced
1. Learning Curve
Coming from Next.js, I had to unlearn some patterns. The biggest shift was understanding when to use .astro
components versus React components.
The mental model shift required rethinking:
- Component choice: When to use Astro components vs. React components
- Data fetching: Understanding build-time vs. runtime data fetching
- Client-side state: Managing state without a full SPA framework
- Routing patterns: Static routing vs. dynamic routing approaches
The documentation helped, but real understanding came from building and experimenting.
2. Debugging Build Issues
Some integrations required specific configurations. For instance, server-side rendering was needed for the Satori OG image generation.
Specific challenges included:
-
Output configuration: Switching from
static
toserver
output for Satori - Font loading: Handling external font fetching in the build process
- Integration conflicts: Ensuring all integrations work together harmoniously
- Build optimization: Fine-tuning build performance with multiple integrations
3. TypeScript Configuration
Getting TypeScript to work perfectly with all the integrations took some tweaking, especially with the strict mode enabled.
The main TypeScript challenges were:
- Content collection types: Understanding generated types from Zod schemas
- Astro component types: Properly typing props and component interfaces
- Integration types: Ensuring all third-party integrations had proper type definitions
- Build configuration: Balancing type safety with build performance
The payoff was worth it - once configured, the TypeScript integration provides excellent developer experience.
Performance Results
The results speak for themselves and exceeded my expectations:
Build Performance
- Build time: Sub-second for my small blog (~2-3 posts)
- Hot reload: Instant updates during development
- Incremental builds: Only changed files are rebuilt
- Memory usage: Significantly lower than Next.js equivalent
Runtime Performance
- Bundle size: Minimal JavaScript shipped to client (under 50kb total)
- Lighthouse scores: 100/100 across all metrics (Performance, Accessibility, Best Practices, SEO)
- First Contentful Paint: Under 1s consistently
- Largest Contentful Paint: Under 1.5s
- Cumulative Layout Shift: 0 (no layout shifts)
Network Performance
- Time to Interactive: Under 2s on 3G connections
- Total Blocking Time: Near 0ms
- Resource loading: Optimized image loading with proper sizing
- Caching: Aggressive caching of static assets
These metrics are particularly impressive considering the blog includes:
- Custom fonts (Inter)
- High-quality images
- Search functionality
- Interactive components
- Automatic OG image generation
The performance gains over traditional React-based solutions are substantial and immediately noticeable.
Would I Use Astro Again?
Absolutely. The developer experience is fantastic, and the performance benefits are real. Astro hits the sweet spot between static site generators and full-featured frameworks.
Perfect Use Cases for Astro
- Content-heavy sites: Blogs, documentation, marketing sites
- Performance-critical applications: Where every millisecond matters
- Multi-framework projects: Teams with diverse framework preferences
- SEO-focused sites: Where server-side rendering is essential
When I'd Consider Alternatives
- Heavy interactive applications: Complex dashboards or SPAs
- Real-time applications: Chat apps, collaborative tools
- Backend-heavy projects: Where you need extensive API routes
Key Takeaways
- Content collections are game-changers - Type-safe content management without a headless CMS complexity
- Island architecture scales well - Add interactivity only where needed, reducing bundle size dramatically
- Modern tooling integration is seamless - TailwindCSS 4, React 19, TypeScript all work perfectly together
- Performance is built-in - No need for complex optimizations or performance tuning
- Developer experience rivals Next.js - Hot reload, TypeScript support, and excellent tooling
- Build times are exceptional - Fast builds mean faster development cycles
- Deployment flexibility - Can deploy as static site or server-rendered application
Final Thoughts
Astro 5 represents a maturation of the static site generation approach. It successfully combines the performance benefits of static sites with the developer experience of modern frameworks. The component islands architecture is particularly clever, solving the common problem of shipping too much JavaScript to the client.
For developers building content-focused sites, Astro 5 should be at the top of your consideration list. The learning curve is manageable, the performance benefits are immediate, and the developer experience is genuinely enjoyable.
If you're building content-heavy sites and want excellent performance without sacrificing developer experience, Astro 5 is definitely worth trying.
Top comments (0)