Org-mode website

At the end of January I re-did my website: I switched from WordPress to a static web site (i.e. a web site that's just HTML files/content, with no programs running on the server).

For many years prior, I'd been running a WordPress site, but I virtually never wrote anything. I diligently upgraded modules and the WordPress core, but the signal:noise ratio was low. That is, I was spending very little time being creative ("signal") and lots of time maintaining the site ("noise").

At the same time, my web site is really simple. Even my WordPress theme selection was simple. So I wasn't using much of the power of WordPress.

I'd been thinking about switching to a static site but I'd prefer not having to write raw HTML or Javascript. When reading about Org-mode, I saw people were using it to generate web sites. I wanted a way to create a web site from org-mode without having to write a lot of elisp, and I found this article, Using org to Blog with Jekyll. And more or less, that's what I set up! Below I describe what I'm currently doing.

File system setup

My file structure is laid out like this:

  (other stuff Jekyll created)


Emacs initial configuration

Before writing any posts, I needed to set up org-mode to be able to publish content. Also a couple of days in I realized I needed a way to link between blog posts that would use Jekyll's "post_url".

In my init.el:

  ;; This lets you publish a "project" via org-mode export:
  (setq org-publish-project-alist '(
    ;; This defines how to publish the org-mode content:
            ;; Path to your org files.
            :base-directory "~/path/to/org/"
            :base-extension "org"

            ;; Path to your Jekyll project.
            :publishing-directory "~/path/to/org/"
            :recursive t
            :publishing-function org-html-publish-to-html
            :headline-levels 4
            :html-extension "html"
            :body-only t ;; Only export section between <body> </body>
            :with-toc nil
            :auto-preamble nil
    ;; This defines how to publish non-org-mode content e.g. txt files
            :base-directory "~/path/to/org/"
            :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf\\|php\\|mov\\|html\\|txt\\|"
            :publishing-directory "~/path/to/org/"
            :recursive t
            :publishing-function org-publish-attachment)

    ;; This says "" is both of the above:
    ("" :components ("org-jb" "org-static-jb"))

    ; creates a new link-type, "jekyll-post". I don't have autocompletion
    ; working for this yet.
    (defun org-jekyll-post-link-follow (path)
      (org-open-file-with-emacs path))
    (defun org-jekyll-post-link-export (path desc format)
       ((eq format 'html)
        (format "<a href=\"{%% post_url %s %%}\">%s</a>" (file-name-sans-extension path) desc))))

    (org-add-link-type "jekyll-post" 'org-jekyll-post-link-follow 'org-jekyll-post-link-export)

The above gets me to the point where C-c C-e (Org-mode exporting) has an option P p for publishing the current project.

Writing the post

I go into org/ and create a new post named, e.g.

When I first started I created a yasnippet (with C-c & C-n). I insert this snippet with C-c & C-s:

  #+OPTIONS: num:nil
  layout: posts
  title: $1
  excerpt: $2

The snippet lets me type the title ($1), then hit tab to go to the excerpt ($2), and then go to the body ($0).

Note well: I had read that you could use BEGIN_HTML for the above instead of BEGIN_EXPORT, but you can't. BEGIN_EXPORT will put content at the absolute beginning of the resulting HTML file, which is what Jekyll needs for reading the content as Jekyll "frontmatter."

At this point I use org-mode formatting. The only special thing I've done is add #+TOC: headlines 3 to longer articles if I want a table of contents.

Publishing to Jekyll

Configuring Jekyll

When I first set up this environment, I installed Jekyll and then ran jekyll new:

cd ~/path/to/org/
jekyll new jekyll

This creates a bunch of stuff in a directory that I have boringly named jekyll, such as a starter _config.yml file.

I then edited this config file, setting properties such as:

  • title
  • email
  • url
  • description
  • theme
  • header_pages
  • twitter_username (theme-specific property)

At that point, Jekyll was ready to receive HTML files published from org-mode.

Sending the org-mode file to Jekyll

Within the org-mode blog post, I save the file and then hit C-c C-e P p. This makes org-mode generate html files from the org files, putting them in the :publishing-directory specified in init.el. Thus org/ is compiled into org/ by Emacs.

At this point I can go into the Terminal to run a jekyll server if I want:

cd ~/path/to/org/
jekyll serve

This starts a web server, by default, where I can see the site. If all looks good then I'm ready to have Jekyll do its own export to the _site/ directory.

Building the Jekyll content

This step is relatively simple:

cd ~/path/to/org/
JEKYLL_ENV=production jekyll build

This "compiles" the Jekyll content and puts it into _site/. The JEKYLL_ENV variable is used by my theme at least to know whether to add Google Analytics code into the site. The content built by jekyll build should be the same as what you see with jekyll serve.

Uploading the final content

At this point all I need to do is send the content to my web server, which I can do with rsync:

cd ~/path/to/org/
rsync -avz _site/

Note well: Any time you use rsync without --delete you have to be kind of careful; if you delete a post, it won't be deleted from the server! I probably should change this.


I love make, so of course I built a quick Makefile to build and publish my site:

        JEKYLL_ENV=production jekyll build

pushup: site
        rsync -avz _site/

Voila! It's that easy! :-P

Bonus: Converting WordPress posts into Jekyll

As a bonus I used the WordPress to Jekyll Exporter plugin for WordPress to export my old posts. These actually sit in my jekyll/_posts/ folder without existing in org/_posts/, which I think makes sense because I'm not going to go back and edit them beyond fixing obvious errors.

Oh and also everything is in git.


Special thanks to…