Pelican
Mature Python-based SSG. Over 12k GitHub stars, ideal for blog construction in Python ecosystem.
トレンド・動向
Stable position as blog construction standard in Python community. Popular for data science blogs.
# SSG
Pelican
## Overview
Pelican is one of the most reliable SSGs in the Python ecosystem, developed as "a mature static site generator written in Python". Supporting both Markdown and reStructuredText, it enables flexible customization through the Jinja2 template engine. With over 12k GitHub stars, it has established a stable position as the standard for blog construction in the Python community. With its rich plugin ecosystem, mature documentation, and long-term support, it has been widely adopted from technical blogs by data scientists and researchers to corporate documentation sites.
## Details
Pelican 2025 edition boasts over 10 years of development history as the definitive Python static site generator. It features Pythonic configuration management through simple configuration files (pelicanconf.py) and advanced customization via the powerful Jinja2 template system. Supporting both Markdown and reStructuredText formats, it allows choosing the notation preferred for technical documentation. Standard features include tag and category classification, RSS feed generation, automatic sitemap generation, and multilingual site support - everything needed for blog operation. The plugin system provides abundant extension features such as search functionality, comment systems, and image optimization.
### Key Features
- **Python-based**: Integrable with Python's rich ecosystem
- **Flexible Templates**: Advanced customization with Jinja2
- **Multi-format Support**: Markdown, reStructuredText, and HTML support
- **Rich Metadata**: Detailed article classification and management features
- **Theme System**: Abundant themes and customizability
- **Plugin Extensions**: Active plugin ecosystem
## Pros and Cons
### Pros
- High extensibility through complete integration with Python ecosystem
- Mature documentation and abundant tutorials and learning resources
- Advanced technical documentation creation with reStructuredText support
- Flexible site design and customization through Jinja2 templates
- SEO optimization and feature extension through abundant plugins
- Low learning cost and intuitive operation for Python developers
### Cons
- Python environment setup required, complex configuration for beginners
- Build times tend to be longer than other SSGs for large sites
- Constraints in integration with other language ecosystems
- Limited support for latest frontend trends
- Constraints in collaboration with modern JavaScript frameworks
- May lag in build speed compared to Node.js-based SSGs
## Reference Pages
- [Pelican Official Site](https://getpelican.com/)
- [Pelican Documentation](https://docs.getpelican.com/)
- [Pelican GitHub Repository](https://github.com/getpelican/pelican)
## Code Examples
### Installation and Basic Setup
```bash
# Install Pelican
pip install pelican[markdown]
# Install with Markdown support (recommended)
pip install pelican[markdown] typogrify
# Initialize project
mkdir my-blog
cd my-blog
pelican-quickstart
# Check basic directory structure
ls -la
# content/ # Article files
# output/ # Generated site
# pelicanconf.py # Configuration file
# publishconf.py # Production configuration
```
### Project Structure and Page Creation
```bash
# Create article directories
mkdir content/articles
mkdir content/pages
mkdir content/images
# Create first article
cat > content/articles/first-post.md << 'EOF'
Title: My First Article
Date: 2025-01-01 10:00
Category: Tech
Tags: pelican, python, blog
Slug: first-post
Author: Your Name
Summary: My first article created with Pelican.
# Introduction
I created a static site using Pelican.
## Features
- Python-based SSG
- Markdown support
- Rich themes
[Read more...](/articles/second-post.html)
EOF
# Create static page
cat > content/pages/about.md << 'EOF'
Title: About
Date: 2025-01-01
Slug: about
This is the about page for this site.
## Profile
Running a technical blog.
## Contact
- Email: [email protected]
- Twitter: @example
EOF
# Generate site and preview
pelican content
pelican --listen # Preview at http://localhost:8000
```
### Configuration Customization (pelicanconf.py)
```python
#!/usr/bin/env python
# -*- coding: utf-8 -*- #
AUTHOR = 'Your Name'
SITENAME = 'My Tech Blog'
SITEURL = ''
PATH = 'content'
TIMEZONE = 'America/New_York'
DEFAULT_LANG = 'en'
# Feed generation
FEED_DOMAIN = SITEURL
FEED_ALL_ATOM = 'feeds/all.atom.xml'
CATEGORY_FEED_ATOM = 'feeds/category_%s.atom.xml'
TRANSLATION_FEED_ATOM = None
AUTHOR_FEED_ATOM = None
AUTHOR_FEED_RSS = None
# Pagination settings
DEFAULT_PAGINATION = 10
PAGINATION_PATTERNS = (
(1, '{base_name}/', '{base_name}/index.html'),
(2, '{base_name}/page/{number}/', '{base_name}/page/{number}/index.html'),
)
# Theme and style settings
THEME = 'themes/elegant' # Custom theme
THEME_STATIC_DIR = 'theme'
CSS_FILE = 'main.css'
# URL and path settings
ARTICLE_URL = 'articles/{slug}.html'
ARTICLE_SAVE_AS = 'articles/{slug}.html'
PAGE_URL = '{slug}.html'
PAGE_SAVE_AS = '{slug}.html'
CATEGORY_URL = 'category/{slug}.html'
CATEGORY_SAVE_AS = 'category/{slug}.html'
TAG_URL = 'tag/{slug}.html'
TAG_SAVE_AS = 'tag/{slug}.html'
# Static path settings
STATIC_PATHS = ['images', 'extra/CNAME', 'extra/robots.txt']
EXTRA_PATH_METADATA = {
'extra/CNAME': {'path': 'CNAME'},
'extra/robots.txt': {'path': 'robots.txt'},
}
# Social links
SOCIAL = (('GitHub', 'https://github.com/username'),
('Twitter', 'https://twitter.com/username'),
('LinkedIn', 'https://linkedin.com/in/username'))
# Menu settings
MENUITEMS = (
('Archives', '/archives.html'),
('Categories', '/categories.html'),
('Tags', '/tags.html'),
)
# SEO settings
SITEMAP = {
'format': 'xml',
'priorities': {
'articles': 0.5,
'indexes': 0.5,
'pages': 0.5
},
'changefreqs': {
'articles': 'monthly',
'indexes': 'daily',
'pages': 'monthly'
}
}
# Default metadata values
DEFAULT_METADATA = {
'status': 'draft',
}
# Markdown settings
MARKDOWN = {
'extension_configs': {
'markdown.extensions.codehilite': {'css_class': 'highlight'},
'markdown.extensions.extra': {},
'markdown.extensions.meta': {},
'markdown.extensions.toc': {},
},
'output_format': 'html5',
}
```
### Plugin System Usage
```python
# pelicanconf.py - Plugin configuration
PLUGIN_PATHS = ['plugins']
PLUGINS = [
'sitemap', # Sitemap generation
'tag_cloud', # Tag cloud
'related_posts', # Related posts
'tipue_search', # Search functionality
'neighbors', # Previous/next article navigation
'representative_image', # Representative image
'liquid_tags.img', # Image tags
'render_math', # Math rendering
]
# Search functionality settings
DIRECT_TEMPLATES = ['index', 'tags', 'categories', 'archives', 'search']
# Tag cloud settings
TAG_CLOUD_STEPS = 4
TAG_CLOUD_MAX_ITEMS = 100
TAG_CLOUD_SORTING = 'random'
# Related posts settings
RELATED_POSTS_MAX = 5
RELATED_POSTS_SKIP_SAME_CATEGORY = True
# Math settings
MATH_JAX = {
'align': 'left',
'indent': '2em',
'show_menu': 'true',
'process_escapes': 'true',
'latex_preview': 'tex2jax_preview',
'color': 'black',
'linebreaks': 'false',
}
# Image processing settings
IMAGE_PROCESS = {
'article-image': ["scale_in 300 300 True"],
'thumb': ["crop 0 0 50% 50%", "scale_out 150 150 True", "crop 0 0 150 150"],
}
# Custom plugin function
def my_custom_filter(text):
"""Custom Jinja2 filter"""
return text.replace('old', 'new')
JINJA_FILTERS = {
'my_filter': my_custom_filter
}
```
### Theme and Template Customization
```html
<!-- themes/custom/templates/base.html -->
<!DOCTYPE html>
<html lang="{{ DEFAULT_LANG }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{{ SITENAME }}{% endblock title %}</title>
<!-- SEO meta tags -->
<meta name="description" content="{% block description %}{{ SITEDESCRIPTION }}{% endblock %}">
<meta name="keywords" content="{% block keywords %}{{ SITEKEYWORDS }}{% endblock %}">
<meta name="author" content="{{ AUTHOR }}">
<!-- OGP settings -->
<meta property="og:title" content="{% block og_title %}{{ SITENAME }}{% endblock %}">
<meta property="og:description" content="{% block og_description %}{{ SITEDESCRIPTION }}{% endblock %}">
<meta property="og:type" content="{% block og_type %}website{% endblock %}">
<meta property="og:url" content="{{ SITEURL }}{% block og_url %}{% endblock %}">
<meta property="og:image" content="{% block og_image %}{{ SITEURL }}/images/default-og.png{% endblock %}">
<!-- Stylesheets -->
<link rel="stylesheet" href="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/css/{{ CSS_FILE }}">
<link rel="stylesheet" href="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/css/pygments.css">
<!-- Favicon -->
<link rel="icon" type="image/x-icon" href="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/images/favicon.ico">
<!-- Feeds -->
{% if FEED_ALL_ATOM %}
<link href="{{ SITEURL }}/{{ FEED_ALL_ATOM }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Atom Feed" />
{% endif %}
</head>
<body>
<header class="site-header">
<div class="container">
<nav class="site-nav">
<a href="{{ SITEURL }}/" class="site-title">{{ SITENAME }}</a>
<ul class="nav-menu">
{% for title, link in MENUITEMS %}
<li><a href="{{ link }}">{{ title }}</a></li>
{% endfor %}
</ul>
</nav>
</div>
</header>
<main class="site-main">
<div class="container">
{% block content %}{% endblock %}
</div>
</main>
<footer class="site-footer">
<div class="container">
<p>© {{ date.strftime('%Y') }} {{ AUTHOR }}. All rights reserved.</p>
{% if SOCIAL %}
<div class="social-links">
{% for name, link in SOCIAL %}
<a href="{{ link }}" target="_blank">{{ name }}</a>
{% endfor %}
</div>
{% endif %}
</div>
</footer>
<!-- JavaScript -->
<script src="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/js/main.js"></script>
{% block scripts %}{% endblock %}
</body>
</html>
<!-- themes/custom/templates/article.html -->
{% extends "base.html" %}
{% block title %}{{ article.title }} - {{ super() }}{% endblock %}
{% block description %}{{ article.summary|striptags|truncate(160) }}{% endblock %}
{% block og_title %}{{ article.title }}{% endblock %}
{% block og_description %}{{ article.summary|striptags|truncate(160) }}{% endblock %}
{% block og_type %}article{% endblock %}
{% block og_url %}{{ SITEURL }}/{{ article.url }}{% endblock %}
{% block content %}
<article class="post">
<header class="post-header">
<h1 class="post-title">{{ article.title }}</h1>
<div class="post-meta">
<time datetime="{{ article.date.isoformat() }}">{{ article.locale_date }}</time>
{% if article.category %}
<span class="post-category">
<a href="{{ SITEURL }}/{{ article.category.url }}">{{ article.category }}</a>
</span>
{% endif %}
{% if article.tags %}
<div class="post-tags">
{% for tag in article.tags %}
<a href="{{ SITEURL }}/{{ tag.url }}" class="tag">{{ tag }}</a>
{% endfor %}
</div>
{% endif %}
</div>
</header>
<div class="post-content">
{{ article.content }}
</div>
<footer class="post-footer">
{% if article.modified %}
<p class="post-modified">
Last updated: <time datetime="{{ article.modified.isoformat() }}">{{ article.modified.strftime('%Y-%m-%d') }}</time>
</p>
{% endif %}
<!-- Related posts -->
{% if article.related_posts %}
<section class="related-posts">
<h3>Related Posts</h3>
<ul>
{% for related in article.related_posts %}
<li><a href="{{ SITEURL }}/{{ related.url }}">{{ related.title }}</a></li>
{% endfor %}
</ul>
</section>
{% endif %}
</footer>
</article>
<!-- Previous/Next article navigation -->
{% if article.prev_article or article.next_article %}
<nav class="post-nav">
{% if article.prev_article %}
<a href="{{ SITEURL }}/{{ article.prev_article.url }}" class="post-nav-prev">
← {{ article.prev_article.title }}
</a>
{% endif %}
{% if article.next_article %}
<a href="{{ SITEURL }}/{{ article.next_article.url }}" class="post-nav-next">
{{ article.next_article.title }} →
</a>
{% endif %}
</nav>
{% endif %}
{% endblock %}
```
### Production Deployment Configuration
```python
# publishconf.py - Production configuration
import os
import sys
sys.path.append(os.curdir)
from pelicanconf import *
# Production URL settings
SITEURL = 'https://yourdomain.com'
RELATIVE_URLS = False
# Feed settings
FEED_DOMAIN = SITEURL
FEED_ALL_ATOM = 'feeds/all.atom.xml'
CATEGORY_FEED_ATOM = 'feeds/category_%s.atom.xml'
# Delete development files
DELETE_OUTPUT_DIRECTORY = True
# Google Analytics
GOOGLE_ANALYTICS = 'UA-XXXXXXXX-1'
# Disqus comments
DISQUS_SITENAME = 'your-disqus-sitename'
# Add production plugins
PLUGINS.extend([
'optimize_images',
'gzip_cache',
'webassets',
])
# Asset optimization
WEBASSETS = True
WEBASSETS_CONFIG = [
('SASS_LOAD_PATHS', ['themes/custom/sass']),
('BABEL_PRESETS', 'es2015'),
]
# Image optimization
OPTIMIZE_IMAGES = True
# SSL settings
FORCE_SSL = True
```
### Automation and GitHub Actions Integration
```yaml
# .github/workflows/deploy.yml
name: Deploy Pelican Site
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pelican[markdown] typogrify
pip install -r requirements.txt
- name: Build site
run: |
pelican content -s publishconf.py
- name: Deploy to GitHub Pages
if: github.ref == 'refs/heads/main'
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./output
cname: yourdomain.com
# Makefile - Development tasks
PY?=python3
PELICAN?=pelican
PELICANOPTS=
BASEDIR=$(CURDIR)
INPUTDIR=$(BASEDIR)/content
OUTPUTDIR=$(BASEDIR)/output
CONFFILE=$(BASEDIR)/pelicanconf.py
PUBLISHCONF=$(BASEDIR)/publishconf.py
DEBUG ?= 0
ifeq ($(DEBUG), 1)
PELICANOPTS += -D
endif
RELATIVE ?= 0
ifeq ($(RELATIVE), 1)
PELICANOPTS += --relative-urls
endif
help:
@echo 'Makefile for Pelican Website '
@echo ' '
@echo 'Usage: '
@echo ' make html (re)generate the web site '
@echo ' make clean remove the generated files '
@echo ' make regenerate regenerate files upon modification'
@echo ' make publish generate using production settings'
@echo ' make serve [PORT=8000] serve site at http://localhost:8000'
@echo ' make devserver [PORT=8000] start/restart develop_server.sh '
html:
$(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS)
clean:
[ ! -d $(OUTPUTDIR) ] || rm -rf $(OUTPUTDIR)
regenerate:
$(PELICAN) -r $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS)
serve:
ifdef PORT
$(PELICAN) -l $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -p $(PORT)
else
$(PELICAN) -l $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS)
endif
publish:
$(PELICAN) $(INPUTDIR) -o $(OUTPUTDIR) -s $(PUBLISHCONF) $(PELICANOPTS)
.PHONY: html help clean regenerate serve publish
```