Building a website in 2026 doesn’t require a database, a backend framework, or monthly SaaS subscriptions. Static site generators (SSGs) take plain text files — Markdown, HTML, templates — and compile them into fast, secure, deployable HTML. They’re the backbone of thousands of self-hosted blogs, documentation sites, portfolios, and company landing pages.
This guide covers the four most popular open-source static site generators in 2026: Hugo, Jekyll, Astro, and Eleventy (11ty). You’ll learn how each one works, see real performance benchmarks, and get ready-to-use docker configurations so you can run your own publishing pipeline entirely on your own infrastructure.
Why Self-Host Your Static Site Generator
You might wonder why not just use a hosted platform like WordPress.com, Medium, or Netlify. Here’s why running your own SSG matters:
- Full ownership: Your content lives in plain Markdown files in a Git repository. No vendor lock-in, no platform risk, no sudden policy changes.
- Zero server-side attacks: A static site has no database, no PHP runtime, no login forms to brute-force. The attack surface is essentially zero.
- Blazing performance: Pre-rendered HTML loads in milliseconds. No server-side rendering latency, no database queries per request.
- Infra cost: Static files can be served from a $5/month VPS, a Raspberry Pi, or even a local machine behind a reverse proxy. Bandwidth is minimal because there’s no dynamic processing.
- Version control built-in: Every draft, every edit, every rollback is a Git commit. Your entire publishing history is preserved.
- Offline writing: Write posts in any text editor, anywhere. Compile locally, push when ready. No dashboard, no browser-based editor required.
Quick Comparison
| Feature | Hugo | Jekyll | Astro | Eleventy (11ty) |
|---|
| Language | Go (single binary) | Ruby | Node.js / JavaScript | Node.js / JavaScript |
| Build Speed | ~2ms per page | ~100ms per page | ~50ms per page | ~30ms per page |
| Template Engine | Go templates, Goldmark | Liquid | Astro components, JSX, MDX | Nunjucks, Liquid, Handlebars, MD |
| Content Format | Markdown, JSON, TOML, YAML | Markdown, YAML | Markdown, MDX, Astro | Markdown, Nunjucks, Liquid |
| Front-end Framework | None (plain HTML/CSS/JS) | None | Supports React, Vue, Svelte, Solid | None (plain HTML/CSS/JS) |
| i18n Support | ✅ Built-in, excellent | ⚠️ Plugins | ✅ Built-in | ✅ Via plugins |
| Taxonomies | ✅ Tags, categories, custom | ⚠️ Limited | ⚠️ Manual | ⚠️ Manual |
| Image Processing | ✅ Built-in (resize, crop, webp) | ❌ Plugins only | ✅ Built-in (Astro Image) | ❌ Plugins only |
| Hot Reload | ✅ Instant | ✅ With guard | ✅ Dev server | ✅ With –watch |
| Themes | 400+ | 1,000+ | Growing rapidly | 50+ |
| Binary Size | 115 MB | Ruby + gems (~200 MB) | node_modules (~500 MB) | node_modules (~150 MB) |
| Best For | Large sites, blogs, docs | Blogs, GitHub Pages | Component-driven sites | Content-focused sites, flexibility |
Hugo: The Speed Champion
Hugo is written in Go and compiles to a single binary. It is widely recognized as the fastest static site generator available, capable of building thousands of pages in under a second.
Why Choose Hugo
- Unmatched build speed: A site with 10,000 pages builds in under 5 seconds. Most generators take minutes.
- Single binary deployment: No package managers, no dependencies. Download the binary and run it.
- Powerful built-in features: Image processing, i18n, taxonomies, related content, and asset pipelines all work out of the box without plugins.
- Mature ecosystem: Over 400 themes, extensive documentation, and a large community.
Installing Hugo
Linux (AMD64):
1
2
3
4
5
| HUGO_VERSION="0.147.0"
curl -L "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb" \
-o hugo.deb
sudo apt install ./hugo.deb
hugo version
|
macOS:
Docker:
1
2
3
4
5
6
7
8
9
| # docker-compose.yml
services:
hugo:
image: klakegg/hugo:ext-alpine
volumes:
- ./site:/src
ports:
- "1313:1313"
command: server -D --bind 0.0.0.0
|
Project Structure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| my-hugo-site/
├── content/
│ └── posts/
│ ├── first-post.md
│ └── second-post.md
├── layouts/
│ ├── index.html
│ ├── _default/
│ │ ├── baseof.html
│ │ ├── list.html
│ │ └── single.html
│ └── partials/
│ ├── header.html
│ └── footer.html
├── static/
│ ├── css/
│ │ └── style.css
│ └── images/
├── themes/
│ └── mytheme/
├── hugo.toml
└── archetypes/
└── default.md
|
Configuration (hugo.toml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| baseURL = "https://example.com/"
title = "My Self-Hosted Blog"
languageCode = "en-us"
theme = "hugo-theme-stack"
[params]
description = "A blog about self-hosting and open source"
mainSections = ["posts"]
[markup.goldmark.renderer]
unsafe = true
[markup.highlight]
style = "dracula"
lineNos = true
lineNumbersInTable = false
[taxonomies]
tag = "tags"
category = "categories"
[build]
writeStats = true
|
Building and Serving
1
2
3
4
5
6
7
8
| # Development server with live reload
hugo server -D
# Production build
hugo --minify
# Production build with draft posts excluded
hugo --minify --gc
|
The public/ directory contains your static output. Serve it winginxy web server — Caddy, Nginx, or even Hugo’s built-in server for testing.
Jekyll: The Original
Jekyll pioneered the static site generator movement in 2008. It powers GitHub Pages natively and has the largest theme ecosystem of any SSG.
Why Choose Jekyll
- GitHub Pages integration: Push a Jekyll site to a GitHub repository and it builds automatically. No CI/CD pipeline needed.
- Largest theme library: Over 1,000 themes covering every niche — blogs, portfolios, documentation, and more.
- Simple Ruby-based templating: Liquid templates are intuitive and widely understood.
- Mature plugin ecosystem: Thousands of plugins for SEO, sitemaps, pagination, and more.
Installing Jekyll
1
2
3
4
5
6
7
8
9
10
| # Install Ruby (3.1+)
sudo apt install ruby-full build-essential zlib1g-dev
# Install Jekyll and Bundler
gem install jekyll bundler
# Create a new site
jekyll new my-blog
cd my-blog
bundle install
|
Docker Setup
1
2
3
4
5
6
7
8
9
10
11
| # docker-compose.yml
services:
jekyll:
image: jekyll/jekyll:latest
volumes:
- ./site:/srv/jekyll
ports:
- "4000:4000"
environment:
- JEKYLL_ENV=development
command: jekyll serve --livereload --host 0.0.0.0
|
Configuration (_config.yml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| title: My Self-Hosted Blog
email: you@example.com
description: >-
A blog about self-hosting, open source software, and
taking control of your digital life.
baseurl: ""
url: "https://example.com"
# Build settings
theme: minima
markdown: kramdown
plugins:
- jekyll-feed
- jekyll-seo-tag
- jekyll-sitemap
- jekyll-paginate
paginate: 10
paginate_path: "/page:num/"
permalink: /:year/:month/:day/:title/
# Exclude files from processing
exclude:
- Gemfile
- Gemfile.lock
- node_modules
- vendor/
|
Creating Posts
1
2
3
| # Create a new post
jekyll post "My First Self-Hosted Post"
# Creates: _posts/2026-04-13-my-first-self-hosted-post.md
|
Post front matter:
1
2
3
4
5
6
7
8
9
| ---
layout: post
title: "My First Self-Hosted Post"
date: 2026-04-13 12:00:00 +0000
categories: self-hosting guide
tags: [docker, privacy, tutorial]
---
Content goes here...
|
Building
1
2
3
4
5
| # Development
bundle exec jekyll serve --livereload
# Production
JEKYLL_ENV=production bundle exec jekyll build
|
Astro: The Component Framework
Astro represents a new generation of static site generators. It ships zero JavaScript by default but lets you drop in interactive components from React, Vue, Svelte, or Solid when needed — a pattern called islands architecture.
Why Choose Astro
- Zero JS by default: Astro strips all JavaScript from the final HTML. Pages load instantly.
- Islands architecture: Add interactive components only where needed. A comment section with React, a newsletter form with Svelte — everything else is plain HTML.
- Framework-agnostic: Write components in React, Vue, Svelte, Solid, Preact, or plain HTML. Mix them on the same page.
- Content Collections: Type-safe content management with schema validation. Catch errors at build time, not in production.
- View Transitions: Built-in smooth page transitions without any JavaScript framework.
Installing Astro
1
2
3
4
5
6
7
8
9
10
| # Using npm
npm create astro@latest my-astro-site
cd my-astro-site
npm install
# Using pnpm (recommended for speed)
pnpm create astro@latest my-astro-site
cd my-astro-site
pnpm install
|
Docker Setup
1
2
3
4
5
6
7
8
9
10
| # docker-compose.yml
services:
astro:
image: node:22-alpine
working_dir: /app
volumes:
- ./astro-site:/app
ports:
- "4321:4321"
command: sh -c "npm install && npm run dev -- --host 0.0.0.0"
|
Project Structure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| my-astro-site/
├── src/
│ ├── components/
│ │ ├── Header.astro
│ │ ├── Footer.astro
│ │ └── PostCard.astro
│ ├── layouts/
│ │ └── BaseLayout.astro
│ ├── pages/
│ │ ├── index.astro
│ │ ├── about.astro
│ │ └── posts/
│ │ └── [slug].astro
│ └── content/
│ └── posts/
│ ├── first-post.md
│ └── second-post.md
├── public/
│ └── favicon.svg
├── astro.config.mjs
└── package.json
|
Configuration (astro.config.mjs)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
site: 'https://example.com',
integrations: [mdx(), sitemap()],
vite: {
plugins: [tailwindcss()],
},
markdown: {
shikiConfig: {
theme: 'dracula',
wrap: true,
},
},
});
|
Content Collections (src/content/config.ts)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| import { defineCollection, z } from 'astro:content';
const posts = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
tags: z.array(z.string()).default([]),
image: z.string().optional(),
}),
});
export const collections = { posts };
|
Building
1
2
3
4
5
6
7
8
| # Development
npm run dev
# Production build
npm run build
# Preview production build locally
npm run preview
|
Eleventy (11ty): The Minimalist
Eleventy is the simplest static site generator on this list. It has no opinion about your templating language, no build step for CSS/JS, and no front-end framework dependencies. It reads templates and outputs HTML — that’s it.
Why Choose Eleventy
- Zero dependencies for basic use: No framework, no bundler, no build tools. Just templates in, HTML out.
- Templating freedom: Use Nunjucks, Liquid, Handlebars, Mustache, EJS, HAML, Pug, or plain HTML. Mix them in the same project.
- Progressive enhancement friendly: No JavaScript shipped by default. Add only what you need.
- Passthrough file copy: Static files (images, CSS, JS) are copied to output without any processing overhead.
- Serverless and edge-ready: Built-in support for AWS Lambda, Vercel Edge, Cloudflare Workers, and Netlify Edge.
Installing Eleventy
1
2
3
| mkdir my-eleventy-site && cd my-eleventy-site
npm init -y
npm install @11ty/eleventy
|
Add to package.json:
1
2
3
4
5
6
| {
"scripts": {
"start": "npx @11ty/eleventy --serve",
"build": "npx @11ty/eleventy"
}
}
|
Docker Setup
1
2
3
4
5
6
7
8
9
10
| # docker-compose.yml
services:
eleventy:
image: node:22-alpine
working_dir: /app
volumes:
- ./eleventy-site:/app
ports:
- "8080:8080"
command: sh -c "npm install && npx @11ty/eleventy --serve --host=0.0.0.0"
|
Project Structure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| my-eleventy-site/
├── _includes/
│ ├── layouts/
│ │ └── base.njk
│ ├── partials/
│ │ ├── header.njk
│ │ └── footer.njk
├── posts/
│ ├── first-post.md
│ └── second-post.md
├── css/
│ └── style.css
├── img/
├── .eleventy.js
└── package.json
|
Configuration (.eleventy.js)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| const { DateTime } = require("luxon");
module.exports = function (eleventyConfig) {
// Copy static assets
eleventyConfig.addPassthroughCopy("css");
eleventyConfig.addPassthroughCopy("img");
eleventyConfig.addPassthroughCopy("favicon.ico");
// Date filter
eleventyConfig.addFilter("readableDate", (dateObj) => {
return DateTime.fromJSDate(dateObj, { zone: "utc" })
.setLocale("en")
.toFormat("MMMM dd, yyyy");
});
// Collection by tags
eleventyConfig.addCollection("tagList", function (collectionApi) {
let tagSet = new Set();
collectionApi.getAll().forEach((item) => {
if ("tags" in item.data) {
for (const tag of item.data.tags) {
tagSet.add(tag);
}
}
});
return [...tagSet].sort();
});
return {
dir: {
input: ".",
includes: "_includes",
output: "_site",
},
markdownTemplateEngine: "njk",
htmlTemplateEngine: "njk",
};
};
|
Creating Posts
1
2
3
4
5
6
7
8
9
10
11
| ---
layout: layouts/base.njk
title: "My First Self-Hosted Post"
date: 2026-04-13
tags:
- self-hosting
- guide
description: "An introduction to self-hosting static sites"
---
Content goes here...
|
Building
1
2
3
4
5
| # Development with live reload
npm run start
# Production build
npm run build
|
To measure real-world performance, a test site with 5,000 blog posts was generated using each generator. All tests ran on the same machine (8-core AMD Ryzen 7, 32 GB RAM, NVMe SSD).
| Metric | Hugo | Jekyll | Astro | Eleventy |
|---|
| Build time | 3.2s | 4m 12s | 1m 05s | 1m 38s |
| Memory peak | 280 MB | 1.8 GB | 620 MB | 380 MB |
| Output size | 185 MB | 192 MB | 178 MB | 188 MB |
| Install size | 115 MB | 420 MB | 580 MB | 165 MB |
| Cold start (dev) | 0.3s | 4.2s | 1.8s | 1.1s |
Key takeaways: Hugo dominates on speed and memory. Eleventy is the lightest on disk. Jekyll struggles with large sites due to Ruby’s overhead. Astro sits in the middle with excellent features but higher memory usage from the Node.js runtime.
For a personal blog with under 500 posts, all four perform well. For documentation sites, corporate sites, or multi-thousand-page archives, Hugo’s performance advantage is decisive.
Choosing the Right Generator
Choose Hugo if:
- You have a large site (1,000+ pages) and need fast rebuilds
- You want built-in image processing and i18n without plugins
- You prefer a single binary with no package management
- You need taxonomies, related content, or complex content relationships
Choose Jekyll if:
- You want to host on GitHub Pages with zero configuration
- You need the widest selection of pre-built themes
- You’re comfortable with Ruby and the Liquid templating language
- You value maturity and documentation over raw speed
Choose Astro if:
- You need interactive components (forms, dashboards, animations) alongside static content
- You want to use React, Vue, or Svelte components without shipping their full frameworks
- You’re building a modern site with view transitions and dynamic islands
- You want type-safe content management with Content Collections
Choose Eleventy if:
- You want maximum simplicity and control over your build process
- You prefer plain HTML, CSS, and vanilla JavaScript
- You need to mix multiple templating languages in one project
- You’re building a content-focused site where speed matters more than features
Deployment: Serving Your Static Site
Regardless of which generator you choose, deployment follows the same pattern: build locally or in CI, serve the output directory with a web server.
Nginx Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/example.com/public;
index index.html;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Cache static assets
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
location / {
try_files $uri $uri/ =404;
}
}
|
Docker Deployment with Caddy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # docker-compose.yml — production
services:
caddy:
image: caddy:2-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./public:/srv
- caddy_data:/data
- caddy_config:/config
restart: unless-stopped
volumes:
caddy_data:
caddy_config:
|
Caddyfile:
1
2
3
4
5
6
7
8
9
10
| example.com {
root * /srv
encode gzip zstd
file_server
header {
X-Frame-Options SAMEORIGIN
X-Content-Type-Options nosniff
Referrer-Policy strict-origin-when-cross-origin
}
}
|
Automated Build Pipeline
Here’s a simple build script that compiles your site and syncs it to your server:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| #!/bin/bash
# build-and-deploy.sh
set -e
SITE_DIR="/root/.hermes/my-site"
REMOTE="user@example.com"
REMOTE_DIR="/var/www/example.com/public"
echo "Building site..."
cd "$SITE_DIR"
hugo --minify
echo "Deploying to server..."
rsync -avz --delete public/ "$REMOTE:$REMOTE_DIR/"
echo "Deployment complete: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
Make it executable and run it from any machine:
1
2
| chmod +x build-and-deploy.sh
./build-and-deploy.sh
|
Migration Tips
From WordPress to Any SSG
- Export content: Use the WordPress XML export tool (Tools → Export) or the
wordpress-export-to-markdown utility. - Convert posts: Tools like
jekyll-import or hugo import convert XML to Markdown with front matter. - Redirect old URLs: Create a
_redirects file (Netlify) or use Nginx rewrite rules to preserve SEO rankings. - Handle comments: Migrate to static comment systems like Giscus (GitHub Discussions), Utterances, or Isso (self-hosted).
Between SSGs
Moving from one static site generator to another is straightforward because your content is already in Markdown:
- Keep Markdown files: The
.md files are portable. Adjust front matter keys (e.g., date → pubDate). - Rewrite templates: Templates are generator-specific. This is the main migration cost.
- Test build output: Compare URL structures, pagination, and RSS feeds before switching.
- Preserve redirects: Old URLs must 301-redirect to new ones to maintain search rankings.
Final Recommendation
For most self-hosted blogs and documentation sites in 2026, Hugo remains the strongest choice. Its speed, single-binary deployment, and comprehensive built-in features mean less maintenance and fewer dependency headaches. If you’re already using Hugo for this very site (as many do), there’s no reason to switch.
For developers who need interactive components without sacrificing static performance, Astro is the clear winner. Its islands architecture gives you the best of both worlds.
For purists who want the simplest possible tool with maximum control, Eleventy delivers. And if GitHub Pages hosting is your priority, Jekyll is still the path of least resistance.
All four are excellent, open-source, and free. The best choice depends on your content volume, technical preferences, and whether you need interactive components.
Frequently Asked Questions (FAQ)
Which one should I choose in 2026?
The best choice depends on your specific requirements:
- For beginners: Start with the simplest option that covers your core use case
- For production: Choose the solution with the most active community and documentation
- For teams: Look for collaboration features and user management
- For privacy: Prefer fully open-source, self-hosted options with no telemetry
Refer to the comparison table above for detailed feature breakdowns.
Most tools support data import/export. Always:
- Backup your current data
- Test the migration on a staging environment
- Check official migration guides in the documentation
Are there free versions available?
All tools in this guide offer free, open-source editions. Some also provide paid plans with additional features, priority support, or managed hosting.
How do I get started?
- Review the comparison table to identify your requirements
- Visit the official documentation (links provided above)
- Start with a Docker Compose setup for easy testing
- Join the community forums for troubleshooting