2024-12-21
I built my blog with Hugo/Zola/Jekyll before. But I got bad experience from time to time. For example,
taxonomies
field. So I had to change front matters of all my posts
when migrating to a new SSG. Terrible.And above of all, all these SSG require me to attach a front matter in every page. It is very inconvenient, if I just need … a page. For example, I write my notes in markdown files. I want to build a local website to render them, and then I can read the math expressions more easily. Of course, I don’t have front matters in my notes, as they are just notes.
I want to build a SSG by myself for a long time. I thuoght I could do
that by pandoc, which is very powerful and even
supports non-markdown files, like org
, latex
. The only barrier
blocking me is that I don’t know how to generate atom feed. However,
after reading
its definition, I
found it was much easier than I thought.
So I migrated my posts this week. As I now generate the feed myself, old posts may spam in RSS readers. But I think nobody subscribe my website at present. It should be okay.
Now let me show how I did that.
pandoc
has
its default template,
which is good enough for me. Besides, I don’t want to increase the
migration workload. So I don’t built my own template during the
migration. What I did is to change my CSS file according to the output
of pandoc.
pandoc \
-s \
\
path/to/markdown -o path/to/html \
--template path/to/template
The command to generate web pages is simple and easy. --template
is
not necessary if default template is used. There are some good flags to
customize the default template:
--css
: include custom css url in headers.---include-in-header
: include content of files in headers.--toc
: generate table of content.--include-before
: the content of files here will be included in the
front of <body>
. I place my navbar here.Of course, they can be built by custom templates too.
The building of website can be achieved by Makefile easily. Read https://makefiletutorial.com/ if you are not familiar with that.
I define my posts in posts/
, the generated files in public/
, and
other files in static/
. These file paths can be defined like below.
I define the url of my posts as /posts/some-slug-here/
.
MD_FILES := $(shell find posts posts.md -name '*.md')
CONVERTED_HTML_FILES := $(patsubst %.md, public/%/index.html, $(MD_FILES))
STATIC_FILES := $(shell find static -type f)
PUBLIC_STATIC_FILES := $(patsubst static/%, public/%, $(STATIC_FILES))
It is noted that /posts/
comes from posts.md
instead of
posts/index.md
. It is to make the Makefile rules simpler.
PANDOC := pandoc --toc --include-before templates/navbar.html --css /css/main.css
.DEFAULT_GOAL := all
all: $(PUBLIC_STATIC_FILES) $(CONVERTED_HTML_FILES) public/index.html
public/%: static/%
mkdir -p $@
$< $@
cp
public/%/index.html: %.md templates/navbar.html
mkdir -p $(dir $@)
$(PANDOC) -s -o $@ $<
public/index.html: index.md templates/navbar.html
mkdir -p $(dir $@)
$(PANDOC) -s -o $@ index.md
Unluckily, a dedicated rule for public/index.html
may be necessary, as
it cannot fit public/%/index.md
.
Now the website can be built easily with make
.
It will be helpful to preview the rendered pages locally. I use Python to do that, as it is now installed in every machines.
make -j
python3 -m http.server -d ./public
The only problem is that, the page cannot render again after I change my
files. A trick is to use watch
to auto building, and live.js
to auto
refresh web pages.
I created a file named templates/livejs.html
.
<script src="https://livejs.com/live.js"></script>
Updated my Makefile.
ifdef INCLUDE_LIVEJS
PANDOC += --include-in-header=templates/livejs.html
endif
Then after running the following command, my pages will be built automatically, and the page will auto refresh if I open it in my browser.
watch -n 1 -- make INCLUDE_LIVEJS=1 -j
The feed generation is the troublesome part. I will recommend to do it by packages like gorilla/feeds or python-feedgen. But I don’t want to include lots of dependencies. I decided to generate it by bash.
I need to get meta info defined in markdown files. I don’t want to parse
markdown files myself, so I decided to use existing pandoc
to achieve
that. I created a file named templates/meta.json
with a oneline
content: $meta-json$
. pandoc
can then return meta info in json.
One big problem is that I don’t know how to parse them in parellel by bash script. My solution is to cache the parsed results first and generate the feed later. Thuogh it creates lots of temp files, the performance is much better than iterating my posts in a bash script.
META_JSONS := $(patsubst %.md, metajsons/%.json, $(MD_FILES))
HTML_CONTENT_FILES := $(patsubst %.md, genfeed/content/%.html, $(MD_FILES))
metajsons/%.json: %.md
mkdir -p $(dir $@)
$< --template=templates/meta.json > $@
pandoc
genfeed/content/%.html: %.md
mkdir -p $(dir $@)
$< | jq -Rr @html > $@ pandoc -t html
Then I only need to fetch these cached data during generating. Much easier. The script is here. Luckily, the published date of posts are included in filenames, so sorting them is not a big issue for me.
And it is necessary to include the atom feed into Makefile.
all: some_other_output_files public/atom.xml
public/atom.xml: bin/atomfeed.sh $(META_JSONS) $(HTML_CONTENT_FILES)
mkdir -p public
bin/atomfeed.sh > public/atom.xml
The way of creating atom feed can be used to generated the index of
/posts
if you don’t want to write it manually.
The loss? I spent much time on it. It will be much easier if I keep
using zola or hugo. But it may be worthwhile? I learnt how to use
Makefile a lot during the migration, and learnt the definition of
atom.xml
.
I lost some features other SSGs provide. For example, it is allowed to put some static files in the same directories of markdown files in Hugo and Zola, and my system doesn’t support that at present. And for sure, tags and categories. But I don’t think such features are that necessary, as I don’t write posts that often. A simpler system is more suitable to me.
The performance is worse than Hugo and Zola, but acceptable.
The gain is actually trivial. I won’t recommend any one to do that, as building such system costs time, especially one with lots of features. It just works for me, as I want a SSG not requiring front matters, and in addition to that, I don’t need lots of features.
The generator is very simple, with jq
, bash
, pandoc
, make
. They
are all well maintained. If I want better features someday, I can
rewrite any one of them instead of reading documents of new generators
to check compatibility.
I am very happy with the result.