Best Static Site Generators in 2026: Astro vs Next.js vs Eleventy
·PkgPulse Team
TL;DR
Astro for content sites; Next.js for hybrid apps; Eleventy for pure simplicity. Astro (~2M weekly downloads) pioneered Islands Architecture — ship zero JavaScript by default, hydrate only interactive components. Next.js (~9M) is the SSG + SSR powerhouse with App Router. Eleventy (~200K) is the purist's choice — no build overhead, any template language, pure HTML output. For blogs, docs, and marketing sites in 2026, Astro is the fastest-growing and most compelling choice.
Key Takeaways
- Next.js: ~9M weekly downloads — SSG + SSR + edge, App Router, Vercel-native
- Astro: ~2M downloads — Islands Architecture, framework-agnostic components, fastest builds
- Eleventy: ~200K downloads — zero framework lock-in, fastest build times, pure HTML
- Gatsby — effectively in maintenance mode (Netlify acquisition, low activity)
- Astro Content Collections — typed content with Zod schemas, best-in-class DX
Astro (Islands Architecture)
---
// src/pages/blog/[slug].astro — static page generation
import { getCollection, getEntry } from 'astro:content';
import Layout from '@/layouts/Layout.astro';
import type { GetStaticPaths } from 'astro';
export const getStaticPaths: GetStaticPaths = async () => {
const posts = await getCollection('blog');
return posts.map((post) => ({
params: { slug: post.slug },
props: { post },
}));
};
const { post } = Astro.props;
const { Content, headings } = await post.render();
---
<Layout title={post.data.title} description={post.data.description}>
<article>
<h1>{post.data.title}</h1>
<time>{post.data.publishedAt.toLocaleDateString()}</time>
<!-- Zero JS shipped for content -->
<Content />
</article>
<!-- This React component ONLY hydrates when visible -->
<TableOfContents headings={headings} client:visible />
</Layout>
// Astro Content Collections — typed content with Zod
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content', // markdown/mdx files
schema: z.object({
title: z.string(),
description: z.string(),
publishedAt: z.coerce.date(),
author: z.string(),
tags: z.array(z.string()).default([]),
featured: z.boolean().default(false),
image: z.object({
src: z.string(),
alt: z.string(),
}).optional(),
}),
});
export const collections = { blog };
---
// Islands Architecture — selective hydration
import HeavyChart from '../components/HeavyChart.jsx';
import SearchBar from '../components/SearchBar.react.tsx';
import VideoPlayer from '../components/VideoPlayer.vue';
---
<!-- Static HTML — no JS shipped -->
<HeavyChart data={chartData} />
<!-- Hydrate immediately when JS loads -->
<SearchBar client:load />
<!-- Hydrate only when component enters viewport -->
<HeavyChart data={chartData} client:visible />
<!-- Hydrate only when browser is idle -->
<VideoPlayer src={videoUrl} client:idle />
<!-- Never hydrate (server-render only, even with props) -->
<StaticMap coordinates={coords} client:only="react" />
# Astro — framework-agnostic
# Mix React, Vue, Svelte in the same project
npm install @astrojs/react @astrojs/vue @astrojs/svelte
Next.js (Full Power SSG)
// Next.js App Router — static generation
// app/blog/[slug]/page.tsx
import { getPostBySlug, getAllPosts } from '@/lib/blog';
import { notFound } from 'next/navigation';
import { MDXRemote } from 'next-mdx-remote/rsc';
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map((post) => ({ slug: post.slug }));
}
export async function generateMetadata({ params }: { params: { slug: string } }) {
const post = await getPostBySlug(params.slug);
return {
title: post.title,
description: post.description,
openGraph: { images: [post.image] },
};
}
export default async function PostPage({ params }: { params: { slug: string } }) {
const post = await getPostBySlug(params.slug);
if (!post) notFound();
return (
<article>
<h1>{post.title}</h1>
<MDXRemote source={post.content} />
</article>
);
}
Eleventy (Pure Simplicity)
// .eleventy.js — minimal config
module.exports = function(eleventyConfig) {
// Add custom filters
eleventyConfig.addFilter('dateFormat', (date) => {
return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
});
// Collections
eleventyConfig.addCollection('posts', (collection) => {
return collection.getFilteredByGlob('src/blog/**/*.md')
.sort((a, b) => b.date - a.date);
});
// Passthrough copy (static assets)
eleventyConfig.addPassthroughCopy('src/static');
return {
dir: {
input: 'src',
output: '_site',
},
};
};
---
title: My Blog Post
date: 2026-03-08
tags: [javascript, web]
layout: post.njk
---
Write your Markdown here.
Eleventy supports Nunjucks, Liquid, Handlebars, HTML, JS — any template language.
Build Performance
| SSG | 1K pages | 10K pages | Hot reload |
|---|---|---|---|
| Eleventy | ~1s | ~10s | Instant |
| Astro | ~3s | ~25s | Fast |
| Next.js (SSG) | ~8s | ~60s | Fast (turbopack) |
| Gatsby | ~15s | ~120s+ | Slow |
When to Choose
| Scenario | Pick |
|---|---|
| Blog, docs, marketing site | Astro |
| Mix of static + dynamic routes | Next.js |
| Zero JS, maximum simplicity | Eleventy |
| App + marketing site (one codebase) | Next.js |
| Mix React/Vue/Svelte components | Astro |
| Largest CMS ecosystem | Next.js |
| Personal site, no build overhead | Eleventy |
| E-commerce (ISR + edge) | Next.js |
Compare SSG package health on PkgPulse.
See the live comparison
View astro vs. nextjs on PkgPulse →