Website with org-mode

Update Update Update (06.02.2014)

With newer versions of org-mode (I'm running 8.2.5h from git), some things have changed. Specifically, org-export-... variables have been renamed to org-html.... Keep that in mind! I'll eventually correct this site for newer versions of org-mode.

Update Update

The Sitemap generated by this description is ugly, and missing it's CSS. Thanks to the generous feedback of Daniel Brunner (big hand, ladies and gentlemen, big hand) of, I now have a working and non-ugly sitemap.

The trick is to not directly publish the sitemap, but create a "frame file" around the actual sitemap, and then include the generated sitemap in that frame file. The new file will then look like this:

#+INCLUDE: ../
#+TITLE: Sitemap

Of course, then the name of the generated sitemap needs to be changed to With this, your sitemap will be both existing and non-ugly. Yay!


Since the include file template is not immediately findable in the worg tutorial, I'll just add it here:

#+EMAIL:     <MAIL>
#+STYLE: <link rel="stylesheet" type="text/css" href="css/main.css"/>
#+STYLE: <link rel="stylesheet" type="text/css" href="css/font-lock.css"/>
#+STARTUP:   hidestars

<div class="header">
  <div class="headerbox"><a href="index.html">Home</a></div>
  <div class="headerbox"><a href="projects.html">Projects</a></div>
  <div class="headerbox"><a href="sitemap.html">Sitemap</a></div>
  <div class="headerbox"><a href="about.html">About</a></div>

And remember to create different config files for the different nesting levels by adding the correct number of dots.

Maintaining a website with org-mode

Actually, putting up a website with org-mode is a bit of work. However, once setup, you can forget about fiddling with the setup (or maybe not, whatever floats your boat). This site is also made from org-mode, and here are some tricks to automate some common actions. Note: This is based on the excellent howto from here.

Automatically inserting a template

Using skeletons and some clever elisp, it is possible to automatically generate a header line like this

#+TITLE: Website with org-mode
#+SETUPFILE: ../../
#+INCLUDE: ../../

The setupfile and include directives are used as in the aforementioned tutorial. INCLUDE is used to include the header box you see at the top.

And here's the code:

(setq seeger-website-base-dir (expand-file-name "~/pim/web/src/")) ;; Contains the base directory where the website source is kept.

(defvar seeger-website-base-dir "/home/jeeger/pim/web/src/" "Base directory of website")
(defvar seeger-website-config-name "export-config-")

(defun repeat-string (str times)
    (dotimes (i times)
      (princ str))))

(defun seeger-get-nesting-depth (filename basename)
    (insert (file-relative-name filename basename))
    (goto-line 0)
    (count-matches "/")))

(defun seeger-make-website-conf-path (filename)
  (let ((nesting-level (seeger-get-nesting-depth filename seeger-website-base-dir)))
    (concat (repeat-string "../" (+ nesting-level 1)) seeger-website-config-name (int-to-string nesting-level) ".org")))

(define-skeleton org-website-skeleton "" "Enter Title: "
  "#+TITLE: " str ?\n
  "#+SETUPFILE: " 
  (seeger-make-website-conf-path (buffer-file-name)) ?\n
  "#+INCLUDE: "
  (seeger-make-website-conf-path (buffer-file-name)) ?\n ?\n
  "* " _ )

(defun seeger-org-website-insert-skeleton-maybe ()
  (if (and (buffer-file-name)
           (not (file-remote-p (buffer-file-name))) 
           (string-match (concat "^" seeger-website-base-dir) 
                         (expand-file-name (buffer-file-name))))

(add-hook 'find-file-hook 'auto-insert)
(setq auto-insert-query nil)
(setq auto-insert-alist '((org-mode . seeger-org-website-insert-skeleton-maybe))) ;; To autoinsert the skeleton without asking

That's certainly a handful. What it does is test if you are editing a buffer below the website directory , and if yes, insert the correct headers. This assumes that the setup files are exactly one level below the source directory, like this, for example:

insgesamt 24
drwxr-xr-x 3 jeeger users 4096 21. Feb 13:17 .
drwxr-xr-x 8 jeeger users 4096 24. Jan 16:17 ..
-rw-r--r-- 1 jeeger users  484 10. Feb 14:27
-rw-r--r-- 1 jeeger users  473 10. Feb 14:35
-rw-r--r-- 1 jeeger users  422  9. Feb 21:10
drwxr-xr-x 4 jeeger users 4096 23. Feb 13:17 src

insgesamt 52
drwxr-xr-x 4 jeeger users 4096 23. Feb 13:17 .
drwxr-xr-x 3 jeeger users 4096 21. Feb 13:17 ..
-rw-r--r-- 1 jeeger users  663 10. Feb 15:27
drwxr-xr-x 2 jeeger users 4096 23. Feb 13:16 css
-rw-r--r-- 1 jeeger users  179 10. Feb 14:29
-rw-r--r-- 1 jeeger users  511 24. Jan 17:27
-rw-r--r-- 1 jeeger users 9055 10. Feb 12:43 me.jpg
drwxr-xr-x 2 jeeger users 4096 23. Feb 13:18 projects
-rw-r--r-- 1 jeeger users  154 23. Feb 13:17
-rw-r--r-- 1 jeeger users   82 25. Jan 11:59
-rw-r--r-- 1 jeeger users  442 25. Jan 11:36

Automatically publishing the website

This assumes that you use git for version control. I do, and so the correct method to update the site was a post-update git hook. It looks like this:

rm -rf ~/temp/*
git archive HEAD web/ | tar x --strip-components 1 -C ~/temp/
emacs --batch -l ~/.emacs.d/publish-website
rm -rf ~/temp/*

This just extracts the freshest revision of the `web/` directory and extracts it to temp. --strip-components is needed because otherwise, the files would be saved in ~/tmp/web/src/ etc. Then we run this little elisp:

;; org settings copied from the original file
;; with paths changed to publish from ~/temp/src/ to /var/www/etc...
(org-publish (assoc "website" org-publish-project-alist))

And bing! we have it. When pushing, the source is automatically published to the web directory.

Instantly testing your site

So now it's on the server. However, for testing the site, you don't want to commit, git-push and browse. You want instant feedback. Can do.

Simply use org-publish-project-alist to publish to a local scratch directory and look at it with a local browser. To do that even more quickly, I use this snippet:

(defun seeger-org-website-maybe-bind-f5 ()
  (if (and buffer-file-name
           (string-match (concat "^" seeger-website-base-dir)
                         (expand-file-name (buffer-file-name))))
      (local-set-key [f5] (lambda () (org-publish-all t)))))

That way, I can simply press F5 whenever editing a website file, and the website gets published to a local folder so I can look at it.

Making font-locking work on the remote server

In the writing of this page, I had the problem that font-locking with htmlize (#+begin_src emacs-lisp and similar) worked only in interactive mode. But Carsten has (again) thought of everything, and after some mailing list scrounging, I found this:

Generate a css file (I call it font-lock.css) with org-export-htmlize-generate-css, put it into your css directory. Then set org-export-htmlize-output-type to css. Also, add an additional #+STYLE to your configuration files so the CSS file gets included. This should generate the same font locking on the server as in the interactive emacs instance.


I hope these tips help anyone trying to get org-mode to the web. More tricks will be added as I find them. For feedback, send me a mail to here.

Author: Jan Seeger