🕷 software projects

by ryan davis



Stupid fast static website generation

Table of Contents:

zenweb 3 is a complete rewrite inspired by Jekyll. Jekyll was designed for github pages and does a great job of it. Unfortunately these design decisions don’t apply to the rest of us. Jekyll is painfully slow because it always builds everything and it only allows liquid for templating which unnecessarily restricts our ability to implement features.

zenweb 3 overcomes these issues. It uses rake to manage dependencies and only builds what is needed. It allows for any markup language to be used at any level so you’re not stuck with liquid or anything else you might not like. Extra markup languages are provided via a simple rubygems plugin system.


  • Renderers
    • Incredibly fast static file generation.
    • Uses rake to manage dependencies, so it only builds what you’ve changed.
    • Multiple renderers per file can be easily applied via multiple file extensions. (eg, index.html.md.erb will call render_erb and then render_md to produce index.html)
    • Templates can be applied via each page’s layout configuration.
    • Templates can have templates, allowing for cleanly refactored layouts.
  • Plugins
    • Rubygem-based plugin system allows for infinite flexibility.
    • Ships with erb, markdown, and less plugins.
    • Many more to come.
  • Configuration
    • Directory wide configuration provided via _config.yml files.
    • Every file can have a YAML header overriding configuration from parent directories.
    • Hierarchical configuration system means you have complete control on every file and every directory.

Getting Started

Creating a New Site

  • gem install –pre zenweb
  • gem fetch –pre zenweb-template
  • gem unpack zenweb-template-1.0.0.b1.gem
  • mv zenweb-template-1.0.0.b1/website mynewwebsite
  • rm -r zenweb-template-1.0.0.b1
  • cd mynewwebsite
  • rake
  • rake run
  • open http://localhost:8000/

You can now modify the Rakefile and set HTML_DIR to point to the base directory of your website. If the server is remote, prepend the hostname with a colon:


Once this is set, rake push will use rsync to publish your website quickly and efficiently. See the tasks below for more info of what you can do via rake.

Using zenweb

zenweb is typically driven entirely through rake tasks. Out of the box it provides the following tasks:

Run a webserver and build on the fly.
Publish your website: clean, generate, push
Generate the website.
Push changes to the site.
Remove junk files.
Clean and also remove the generated .site directory.
Debug the generation of a file.


Creating a new page is as simple as creating a new file. Really. That’s all there is to it. You can provide an optional YAML header but the rest of the file is just content. File extensions are used to determine how to render the file and stop rendering when they get to a file extension they don’t know.

For example, say you have the file pages/index.html.md.erb and it looks like this:

title: My Pages

This is my website. There are many like it but this one is mine.

{{ sitemap }}

Broken down:

Lines 1-3
The YAML header and can define anything you want. Here we only define the title of the page. Other configuration values like layout can come from inherited _config.yml files.
Line 5
One simple line of markdown content.
Line 7
Ruby code that creates a sitemap for all pages below this one.

When it comes time to generate this file, render_erb will be called first because it is the last extension on the filename. Next, render_md will be called. Since there is no render_html, rendering stops at this level and the resulting filename will be .site/pages/index.html.

When it comes time to generate this file index.html.md.erb will call render_erb resulting in index.html.md. index.html.md will call render_md resulting in index.html. There is no render_html, so rendering stops at this level. The resulting filename will be .site/pages/index.html.

When it comes time to generate this file:

  • index.html.md.erb will call render_erb resulting in index.html.md.
  • index.html.md will call render_md resulting in index.html.
  • There is no render_html, so rendering stops.
  • The resulting filename will be .site/pages/index.html.
input filename renderer output filename
pages/index.html.md.erb render_erb pages/index.html.md
pages/index.html.md render_md pages/index.html
pages/index.html N/A .site/pages/index.html

But it doesn’t stop there… If layout is defined, then _layouts/<layout-name>* is looked up. The layout is rendered and the contents from your page is injected into the body of the layout. If a layout defines a layout, then the process repeats. This allows you to organize your site as site-layout(section-layout(page-contents)) or however you please.

File Organization

File organization in zenweb is pretty easy and flexible. There is only 1 file required to get started: the Rakefile. It needs to contain:

Rake.application.rake_require "zenweb/tasks"

and nearly everything else is taken care of.

A simple website might contain the following:

Mentioned above.
All generated files are written to .site.
The top level configuration (yaml) file.
Snippets to be used by include statements are stored here.
Layout files are stored here.
The homepage of your site.
Subdirectories can have their own config files and can override values higher up.
A typical blog directory will contain an index file and a bunch of dated posts. These files all render using ERB and markdown.
Regular web pages and subdirectories. zenweb isn’t biased towards blogs.
Generates an Atom feed file for feed reader applications. Uses ERB to generate.
A sitemap.xml file to help search-engines index your content.
A simple static robots.txt file

Comparing to Jekyll

zenweb is quite a bit lighter than jekyll. This can be seen as both a good thing and a bad thing. In some ways, zenweb does less than jekyll. In some ways this gives zenweb more flexibility and certainly more speed.

To objectify this comparison, we can look at the number of dependencies:

gem deps
Jekyll 27
zenweb 8


Things that zenweb borrowed from jekyll:

  • _files and _dirs are not generated, only used to organize shared stuff.

Things zenweb doesn’t do out of the box (yet?):

  • zenweb has no notion of “virtual” pages (yet), so it currently doesn’t do:
    • Pagination
    • Tags / Categories
    • Archive directories
  • zenweb doesn’t do “permalink”.
    • It does have a date_fmt configuration variable that is used when creating files with YYYY-MM-DD in their filenames.
  • zenweb will provide migrations for other systems in the future, but they won’t ship with zenweb itself. Since you only use it once(ish), why include them?

Things zenweb has that jekyll doesn’t:

  • Proper dependency management. Updating a blog post should update the sitemap, index.html, and anything else that could reference the blog post. zenweb uses rake to ensure this happens correctly.
    • Custom dependencies can be easily declared via the extra_deps task.
  • All configuration data is available at all stages of rendering. This is mostly a limitation caused (by design) by liquid.
  • Speed. I’m not entirely sure why, but jekyll is a dog.
  • Flexibility.
    • Liquid holds me back too much. Give me the full power of ruby or whatever else I want for my layout files.
    • zenweb is not oriented towards blogs and really doesn’t care what you’re creating or how it is structured (eg, there is no _posts directory).


  • Jekyll + octopress w/ 747 pages.
  • zenweb w/ 1061 pages.
Build Type Jekyll zenweb
Full Build 146.2s 26.6s
Build 1 page 148.2s 4.9s
Build 0 pages 147.3s 2.3s

Updating a single page on my website (~300 pages) and rsyncing all changes to the server takes me a mere 3.3 seconds with a single rake sync invocation on a semi-slow café network.

Get The Code

If you just want to use zenweb, you can install it via RubyGems:
gem install zenweb
Fork me on GitHub If you want to hack on zenweb, clone it from GitHub:
git clone git://github.com/seattlerb/zenweb

Latest Activity