webdev CMS static site generator python


A few tips and notes on the static site generator pelican

Pelican is a static site generator in python which is not necessarily beautiful out of the box, but offers a lot of control on the generation process.

More practical work environment

Add to ~/.bashrc the following aliases:

1 alias pc="pelican content"
2 alias pl="pelican --listen"

simply build the website py typing pc from the directory, and pl to launch Pelican’s web server.

The website can be build without shutting down the server, so you can simply launch the pl command in another terminal (or typically, in another tmux pane) and build the website on the fly.


This part assumes that you use RestructuredText format for your articles.

Easy styling

to add a class to a paragraph, use (be careful with the whitespaces to avoid errors):

1 .. class:: warning
3 This part assumes that you use RestructuredText format for your articles.

Example: the warning just above (assuming you've created the corresponding CSS class)!

Multiple ..class can be nested, as long as the empty lines are respected. For instance:

1 .. class:: align-right
3 .. class:: side-note
5 **Le Règlement Général sur la Protection des Données (RGPD) c'est quoi ?**

using html within a .rst file


1 .. raw:: html
3    <details>
4    <summary><b>Here is a summary.</b><br>
5    this is a summary that can be clicked to view/hide its details </summary>
6    Even though there is not much to see here.
7    </details>


Here is a summary.
this is a summary that can be clicked to view/hide its details
Even though there is not much to see here.


clean URLs

In the file, set the following variables:

TAGS_URL = "tags"
CATEGORIES_URL = "categories"
ARCHIVES_URL = "archives"

ARTICLE_URL = "{slug}"
ARTICLE_LANG_URL = '{slug}-{lang}'

PAGE_URL = "{slug}"
PAGE_SAVE_AS = "{slug}.html"
PAGE_LANG_URL = 'pages/{slug}-{lang}'

CATEGORY_SAVE_AS = 'categories/{slug}.html'
CATEGORY_URL = 'categories/{slug}'
TAG_SAVE_AS = 'tags/{slug}.html'
TAG_URL = 'tags/{slug}'

The result will be urls of the form or

To ensure that these links are used, you also need to configure the web server. In the case of nginx:

location = / {
  index index.html;

location / {
    root /var/www/;
    try_files $uri.html $uri =404;


Using article "summary"

Use the striptags filter to avoid having <p class = "first"> garbage in your meta description:

1 {% if  article.summary  %}
2 <meta property="og:description" content="{{ article.summary|striptags|safe  }}">
3 {% endif %}

Article ordering

By default articles are ordered in "reverse-date". But any metadata (with a "reverse-" prefix to... reverse) can be used: for instance, this wiki uses "title", like this in

1 ARTICLE_ORDER_BY = "title"

See Ordering content for more details.

Custom home page

To write properly

Do not modify the "index.html" file hoping to change the homepage: this file is also used for the category, tags and archives pages, which will probably be messed up if you change it. Instead, create a custom template.

follow /!template "homepage" must be in content/templates and exclude it, as weird as it is then,

Navigation menu with article list

<-- As an example, this wiki uses this.

Both the default (and most popular) theme "notmyidea" and the basic boiletplate theme "simple" gives a confusing example: the way they iterate on categories is:

1 {% for cat, null in categories %}
2 <li{% if cat == category %} class="active"{% endif %}><a href="{{ SITEURL }}/{{ cat.url }}">{{ cat }}</a></li>
3 {% endfor %}

It would be tempting to add a loop under it iterating every articles and checking if the current category being processed is the same, but as it turns out the return value being negated by the "null" is the list of the articles in the given category. So simply do:

1 {% for cat, category_articles in categories %}
2   <li><a class = "category" href="{{ SITEURL }}/{{ cat.url }}">{{ cat }}</a>
3     <ul>
4         {% for artic in category_articles %}<li><a href = "{{ artic.url }}">{{ artic.title }}</a></li>
5         {% endfor %}
6     </ul></li>
7 {% endfor %}

Articles listed per year

A common way to list articles on personal websites nowaday is to list them by year like so:


  • I saw a nice rock yesterday


  • I'm giving up on that blog
  • More articles to come this year!


  • A long essay on consciousness
  • New blog!

To do so in pelican, here is a skeleton template to use within templates.index.html:

 1 <ul class = "article-list">
 2 {% for year, year_posts in articles_page.object_list|groupby('date.year')|reverse %}
 3   <h2>{{year}}</h2>
 4       {% for article in year_posts %}
 5           <li>
 6             <article>
 7               <header>
 8                 <h2>
 9                   <a href="{{ SITEURL }}/{{ article.url }}" rel="bookmark" title="Permalink to {{ article.title|striptags }}">
10                     {{ article.title }}
11                   </a>
12                 </h2>
13               </header>
14             </article>
15           </li>
16       {% endfor %}
17   {% endfor %}
18 </ul>

Explanation: the heavylifting is done by the articles_page.object_list|groupby('date.year')|reverse which groups articles per year with the newest ones at the top. The resulting list is unpacked as the year + the list of articles for that year, the latter being iterated on by a second loop.