How I use org-publish to create my blog / website
Table of Contents
1. Directory Structure
I keep a git repository for easy and version control. Let's take a look at the one level deep directory tree withing that repo:
tree -d ../..
The build directory is where all of the files necessary for building the website go and dist is where all of the files go after they were exported and compiled by org-publish. It's not included in git commits (part of .gitignore).
src is where all of my source content files live. Let's have a more detailed look at it:
tree -L 2 ..
.. ├── about.org ├── assets │ └── img ├── blog │ ├── 30-day-text-based-browsing-challenge.org │ ├── hello-world.org │ ├── index.org │ ├── my-home-network-backup-architecture.org │ ├── org-publish-setup.org │ └── this-website-is-ugly.org ├── index.org └── links.org 4 directories, 9 files
As you can see all of the pages are individual .org files, with single pages living in the source root and blog posts in the blog directory. The blog/index.html file is dynamic and generated by org-publish as described in the next section.
Files in the assets directory are not compiled to HTML by org-publish, but simply copied over (spoiler: you can achieve this by using org-publish-attachment instead of org-html-publish-to-html as your publishing function).
2. Build files
First, let's take a closer look at my export ELISP file that defines how org bundles the export from .org files to HTML. I have taken a more literate approach with this file so I hope it is obvious from the raw file contents alone what's being done
2.1. export.el
- ☐ setup default org-babel header args to render without CSS
- ☐ include infojs
First, I need to require the ox-publish package which provides org-publish support
(require 'ox-publish)
Because I can't use doom to compile, but have to use the base emacs command, I will now load the babel packages:
(org-babel-do-load-languages 'org-babel-load-languages '((emacs-lisp . t) (shell . t)))
Next, I want all files to be recompiled when I build the site, not only those who changed. If I don't do this, I think changes to my header and footer won't be applied globally unless I edit a file.
- ☐ See if I can disable this / find a workaround to shorten build time
(setq org-publish-use-timestamps-flag nil)
I think it looks ugly if I include raw HTML strings in my publish project list, so I want to define two 'components' that contain pre- and postamble respectively.
This code was generated using ChatGPT. I think it opens a temporary buffer, inserts the file contents and then returns them as a string into the variable. Sounds quite complicated, so maybe there's a smoother way without opening a temporary buffer.
- ☐ Check this
(setq my-preamble
(with-temp-buffer
(insert-file-contents "./preamble.html")
(buffer-string)))
(setq my-postamble
(with-temp-buffer
(insert-file-contents "./postamble.html")
(buffer-string)))
Now I'm ready to define my org-publish 'projects' (which are really collections for related content as far as I can tell)
(setq org-publish-project-alist
;; to properly include my header / footer components.
;; I need to use this backtick instead of the simple tick
;; and then tell org-publish to only take fetch these two variable values using ","
`(("blog"
:html-doctype "html5"
;; ignore files in source root. these are handled by "home" component
:base-directory "../src/blog/"
:publishing-directory "../dist/blog/"
;; only include org files
:base-extension "org"
:publishing-function org-html-publish-to-html
;; load header / footer html files from build directory
:html-preamble ,my-preamble
:html-postamble ,my-postamble
:html-checkbox-type unicode
;; generate sitemap which is used as blog index (renamed to blog/index.html)
:auto-sitemap t
:sitemap-filename "index.org"
:sitemap-title "Blog Posts"
:sitemap-sort-files anti-chronologically
:sitemap-style list
;; strip css
:html-head-include-default-style nil
;; include my data
:author "r1bb0n"
:email "technologika@posteo.net"
:with-email t
)
("home"
;; pretty much the same setup as above
;; just one file level higher to not include single pages as blog posts
:html-doctype "html5"
:base-directory "../src/"
:publishing-directory "../dist/"
:publishing-directory "~/Documents/code/tlkgws-nocss/dist/"
:html-preamble ,my-preamble
:html-postamble ,my-postamble
:html-checkbox-type unicode
:publishing-function org-html-publish-to-html
:html-head-include-default-style nil
:author "r1bb0n"
:email "technologika@posteo.net"
:with-email t
)
("static"
;; static assets
:base-directory "../src/assets"
:publishing-directory "../dist/assets"
:base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf"
:recursive t
;; use this function to just copy over files to dist
:publishing-function org-publish-attachment
)
;; include all components in complete website build
("website" :components ("home" "blog" "static")))
)
To remove CSS from code blocks, I disable htmlize
(setq org-html-htmlize-output-type nil )
3. Building the blog with make
4. Deploying on Neocities
5. To-Dos
- ☑ html components for header & footer
- ☐ file structure with make build workflow
- ☐ org-babel to automatically render codeblocks
- ☐ strip css from exported code blocks
