Posted by


Simple, lightweight, and magic-free static site/blog generator for Python coders

Take full control of your static website/blog generation by writing yourown simple, lightweight, and magic-free static site generator inPython. That’s right! Reinvent the wheel, fellas!

View SourceView DemoMIT License



This repository contains the source code of an example websitecontaining two static blogs and a few static pages. The website can begenerated by running The output looks likethis. That’s it!

So go ahead, fork this repository, replace the content withyour own, and generate your static website. It’s that simple!

You are free to copy, use, and modify this project foryour blog or website, so go ahead and fork this repository and make ityour own project. Change the layout if you wish to, improvethe stylesheet to suit your taste, if you need to, and develop your website/blogjust the way you want it.

But Why?

For fun and profit! Okay, maybe not for profit, but hopefully for fun.

Have you used a popular static site generator like Jekyll to generateyour blog? I have too. It is simple and great. But then did you yearnto use something even simpler to generate your blog? Do you like Python?Perhaps the thought of writing your own static site generator crossedyour mind but you thought it would be too much work? If you answered”yes” to these questions, then this project is for you.

With, you are in full control. There is nohidden magic! There is no need to read any documentation to understandhow it works. There is no need to learn how to write configuration filesto produce some desired effect.


  • The code is the documentation.
  • The code is the configuration.

Everything is laid out as plain and simple Python code for you to readand enhance. It is less than 130 lines of code (excluding comments,docstrings, and blank lines). It gets you off the ground pretty quickly.You only need to execute

You can develop a decent website/blog within a few minutes and then youcan begin tinkering with the source code, thelayout, and the stylesheet tocustomize the look and feel of your website to your satisfaction.

Get Started

This section provides some quick steps to get you off the ground asquickly as possible.

  1. For a quick demo on your local system, just enter this command:

    make serve

    If you don’t have make but have Python 3.x, enter this command:

    python3 makesite.pycd _sitepython3 -m http.server

    Note: In some environments, you may need to use python instead ofpython3 to invoke Python 3.x.

    If you only have Python 2.7, enter this command:

    python makesite.pycd _sitepython -m SimpleHTTPServer

    Then visit http://localhost:8000/. It should look likethis.

    Note: You can run with Python 2.7 orPython 3.x.

  2. You may see a few Cannot render Markdown warning messages in theoutput of the previous command. This is due to the fact that anexample blog in this project has a few posts writtenin Markdown. To render them correctly, install the commonmarkpackage with this command:

    pip install commonmark

    Then try the previous step again.

  3. For an Internet-facing website, you would be hosting the staticwebsite/blog on a hosting service and/or with a web server such asApache HTTP Server, Nginx, etc. You probably only need to generatethe static files and know where the static files are and move themto your hosting location.

    If you have the make command, enter this command to generate yourwebsite:

    make site

    If you don’t have make but have python3, enter this command:


    Note: In some environments, you may need to use python instead ofpython3 to invoke Python 3.x.

    If you only have python, enter this command:


    The _site directory contains the entire generated website. Thecontent of this directory may be copied to your website hostinglocation.

The Code

Now that you know how to generate the static website that comes withthis project, it is time to see what does.You probably don’t really need to read the entire section. The sourcecode is pretty self-explanatory but just in case, you need a detailedoverview of what it does, here are the details:

  1. The main() function is the starting point of website generation.It calls the other functions necessary to get the website generationdone.

  2. First it creates a fresh new _site directory from scratch. Allfiles in the static directory are copied to thisdirectory. Later the static website is generated and written to thisdirectory.

  3. Then it creates a params dictionary with some default parameters.This dictionary is passed around to other functions. These otherfunctions would pick values from this dictionary to populateplaceholders in the layout template files.

    Let us take the subtitle parameter for example. It is setto our example website’s fictitious brand name: “Lorem Ipsum”. Wewant each page to include this brand name as a suffix in the title.For example, the about pagehas “About – Lorem Ipsum” in its title. Now take a look at thepage layout template that is used as the layoutfor all pages in the static website. This layout file uses the{{ subtitle }} syntax to denote that it is a placeholder thatshould be populated while rendering the template.

    Another interesting thing to note is that a content file canoverride these parameters by defining its own parameters in thecontent header. For example, take a look at the content file forthe home page. In its content header, i.e.,the HTML comments at the top with key-value pairs, it defines a newparameter named title and overrides the subtitle parameter.

    We will discuss the syntax for placeholders and content headerslater. It is quite simple.

  4. It then loads all the layout templates. There are 6 of them in thisproject.

    • layout/page.html: It contains the basetemplate that applies to all pages. It begins with and , and ends with . The{{ content }} placeholder in this template is replaced withthe actual content of the page. For example, for the about page,the {{ content }} placeholder is replaced with the the entirecontent from content/about.html. This isdone with the make_pages() calls further down in the code.

    • layout/post.html: It contains the templatefor the blog posts. Note that it does not begin with and does not contain the and tags.This is not a complete standalone template. This templatedefines only a small portion of the blog post pages that arespecific to blog posts. It contains the HTML code and theplaceholders to display the title, publication date, and authorof blog posts.

      This template must be combined with thepage layout template to create the finalstandalone template. To do so, we replace the {{ content }}placeholder in the page layout template withthe HTML code in the post layout template toget a final standalone template. This is done with therender() calls further down in the code.

      The resulting standalone template still has a {{ content }}placeholder from the post layout templatetemplate. This {{ content }} placeholder is then replacedwith the actual content from the blog posts.

    • layout/list.html: It contains the templatefor the blog listing page, the page that lists all the posts ina blog in reverse chronological order. This template does not domuch except provide a title at the top and an RSS link at thebottom. The {{ content }} placeholder is populated with thelist of blog posts in reverse chronological order.

      Just like the post layout template , thistemplate must be combined with thepage layout template to arrive at the finalstandalone template.

    • layout/item.html: It contains the templatefor each blog post item in the blog listing page. Themake_list() function renders each blog post item with thistemplate and inserts them into thelist layout template to create the bloglisting page.

    • layout/feed.xml: It contains the XML templatefor RSS feeds. The {{ content }} placeholder is populated withthe list of feed items.

    • layout/item.xml: It contains the XML template foreach blog post item to be included in the RSS feed. Themake_list() function renders each blog post item with thistemplate and inserts them into thelayout/feed.xml template to create thecomplete RSS feed.

  5. After loading all the layout templates, it makes a render() callto combine the post layout template with thepage layout template to form the finalstandalone post template.

    Similarly, it combines the list layout templatetemplate with the page layout template to formthe final list template.

  6. Then it makes two make_pages() calls to render the home page and acouple of other site pages: the contact pageand the about page.

  7. Then it makes two more make_pages() calls to render two blogs: onethat is named simply blog and another that is namednews.

    Note that the make_pages() call accepts three positionalarguments:

    • Path to content source files provided as a glob pattern.
    • Output path template as a string.
    • Layout template code as a string.

    These three positional arguments are then followed by keywordarguments. These keyword arguments are used as template parametersin the output path template and the layout template to replace theplaceholders with their corresponding values.

    As described in point 2 above, a content file can override theseparameters in its content header.

  8. Then it makes two make_list() calls to render the blog listingpages for the two blogs. These calls are very similar to themake_pages() calls. There are only two things that are differentabout the make_list() calls:

    • There is no point in reading the same blog posts again that wereread by make_pages(), so instead of passing the path tocontent source files, we feed a chronologically reverse-sortedindex of blog posts returned by make_pages() to make_list().
    • There is an additional argument to pass theitem layout template as a string.
  9. Finally it makes two more make_list() calls to generate the RSSfeeds for the two blogs. There is nothing different about thesecalls than the previous ones except that we use the feed XMLtemplates here to generate RSS feeds.

To recap quickly, we create a _site directory to write the static sitegenerated, define some default parameters, load all the layouttemplates, and then call make_pages() to render pages and blog postswith these templates, call make_list() to render blog listing pagesand RSS feeds. That’s all!

Take a look at how the make_pages() and make_list() functions areimplemented. They are very simple with less than 20 lines of code each.Once you are comfortable with this code, you can begin modifying it toadd more blogs or reduce them. For example, you probably don’t need anews blog, so you may delete the make_pages() and make_list() callsfor 'news' along with its content at content/news.


In this project, the layout template files are located in the layoutdirectory. But they don’t necessarily have to be there. You canplace the layout files wherever you want and accordingly.

The source code of that comes with thisproject understands the notion of placeholders in the layout templates.The template placeholders have the following syntax:

{{  }}

Any whitespace before {{, around , and after }} is ignored.The should be a valid Python identifier. Here is an example oftemplate placeholder:

{{ title }}

This is a very simple template mechanism that is implemented already inthe For a simple website or blog, thisshould be sufficient. If you need a more sophisticated template enginesuch as Jinja2 orCheetah, you need to to add support for it.


In this project, the content files are located in the contentdirectory. Most of the content files are written in HTML.However, the content files for the blog named blog arewritten in Markdown.

The notion of headers in the content files is supported Each content file may begin with one or moreconsecutive HTML comments that contain headers. Each header has thefollowing syntax:

Any whitespace before, after, and around the tokens are ignored. Here are some example headers:

It looks for the headers at the top of every content file. As soon assome non-header text is encountered, the rest of the content from thatpoint is not checked for headers.

By default, placeholders in content files are not populated duringrendering. This behaviour is chosen so that you can write content freelywithout having to worry about makesite interfering with the content,i.e., you can write something like {{ title }} in the content andmakesite would leave it intact by default.

However if you do want to populate the placeholders in a content file,you need to specify a parameter named render with value of yes. Thiscan be done in two ways:

  • Specify the parameter in a header in the content file in thefollowing manner:

  • Specify the parameter as a keyword argument in make_pages call.For example:

    blog_posts = make_pages('content/blog/*.md',                        '_site/blog/{{ slug }}/index.html',                        post_layout, blog='blog', render='yes',                        **params)


Thanks to:

  • Susam Pal for the initial documentationand the initial unit tests.
  • Keith Gaughan for an improvedsingle-pass rendering of templates.


This is free and open source software. You can use, copy, modify,merge, publish, distribute, sublicense, and/or sell copies of it,under the terms of the MIT License.

This software is provided “AS IS”, WITHOUT WARRANTY OF ANY KIND,express or implied. See the MIT License for details.


To report bugs, suggest improvements, or ask questions, please visit

To restore the repository download the bundle


and run:

 git clone sunainapai-makesite_-_2019-11-25_19-28-31.bundle 

Uploader: sunainapai
Upload date: 2019-11-25