Bootstrapping a Blog
November 18th 2022
I’ve tried many static site generators in my time. They all have a great number of opinions, and to their detriment and mine we often don’t see eye-to-eye. I’m going to attempt building the most minimal possible blog I can, and I will write the development process out as I build the blog and deploy it in time with the development process. As I write this paragraph the blog doesn’t exist, and this will be its first post.
My goal is to take the file I’m writing at the moment (a markdown file), convert it into html, and “serve” it with the open command.
MVP first:
Let’s grab the markdown file, run the conversion into HTML, and see if it renders in a browser:
λ cd blog
~/blog
λ ls
making_a_static_blog_from_scratch.mdI don’t remember the exact usage of pandoc so:
λ tldr pandoc
Convert documents between various formats.
More information: <https://pandoc.org>.
Convert file to PDF (the output format is determined by file extension):
pandoc input.md -o output.pdf
Force conversion to use a specific format:
pandoc input.docx --to gfm -o output.md
Convert to a standalone file with the appropriate headers/footers (for LaTeX, HTML, etc.):
pandoc input.md -s -o output.tex
List all supported input formats:
pandoc --list-input-formats
List all supported output formats:
pandoc --list-output-formatstldr to the rescue.
Fortunately, pandoc has the easiest usage ever:
λ pandoc making_a_static_blog_from_scratch.md -o first_post.html
λ ls first_post.html
first_post.htmland
λ open first_post.hmlLo and behold:
Um… actually I’m not sure I have any way of embedding images in these documents.
Time to google some basic markdown syntax

Clicking on the second link provides a nice insight into the syntax, so now I’ll follow that and add this line to the file:
![searching basic markdown syntax like a n00b]()But, alas, where to link the image? Probably time to make an images directory for this blog:
λ mkdir assets
λ mkdir assets/imagesThat should come in handy.
Now, time to see if we can actually link them. I took a screenshot of me making the search so:
λ mv ~/Desktop/brave_search_md_syntax.png assets/images
/Users/alex/Desktop/brave_search_md_syntax.png -> assets/images/brave_search_md_syntax.pngNow perhaps if I enter the relative path the link will work:
λ pandoc making_a_static_blog_from_scratch.md -o second_render.html
λ open second_render.htmlAha! Now! Lo and Behold: 
This deal gets more recursive all the time.
Elements of Style
I detest unstyled html pages (lookin’ at you Richard Stallman). So let’s beef this thing up shall we?
For some time I’ve wanted to build a blog using Tufte CSS. I’m a big fan of the side column with footnotes. (This will probably prevent me from writing so many parenthetical statements). In fact, it would be cool to automate away my parenthetical statements into footnotes with a script or something.
In the mean time, let’s see how much style we can get away with by pasting a simple link to the public style sheet.
λ mkdir assets/stylesheets
λ curl https://raw.githubusercontent.com/edwardtufte/tufte-css/gh-pages/tufte.css > ass
ets/stylesheets/tufte.css
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 11212 100 11212 0 0 41069 0 --:--:-- --:--:-- --:--:-- 41069
λ ls assets/stylesheets
tufte.css(There’s a minified version but I’ll grab it later, I’ll keep the large one, easier to tweak)
Let’s look at what pandoc generated shall we?
λ ll *.html
.rw-r--r-- alex staff 1.8 KB Fri Nov 18 21:52:48 2022 first_post.html
.rw-r--r-- alex staff 3.6 KB Fri Nov 18 22:14:42 2022 second_render.html
.rw-r--r-- alex staff 3.6 KB Fri Nov 18 22:14:48 2022 third_render.html
λ bat third_render.html
───────┬────────────────────────────────────────────────────────────────────────────────
│ File: third_render.html
───────┼────────────────────────────────────────────────────────────────────────────────
1 │ <h1 id="bootstrapping-a-blog">Bootstrapping a Blog</h1>
2 │ <h2 id="november-18th-2022">November 18th 2022</h2>
3 │ <p>I’ve tried many static site generators in my time. They all have far too man
│ y opinions. I decided to build the most minimal possible blog I could. I’m writ
│ ing this article as I build the blog and deploy it. In real time. So as I write
│ this paragraph. The blog doesn’t exist. This will be the first post on the blo
│ g.</p>
4 │ <p>I’ve started by doing the sensible thing at the beginning of any project, an
│ d opened my terminal. My goal for tonight is to take this file I’m writing at t
│ he moment, convert it into html, and “serve” it with the <code>open</code> comm
│ and. Here are a few things I’ll need:</p>
...I’m pleasantly surprised. Pandoc didn’t generate a full html file with a doctype declaration, body etc. It only translated the direct elements like headers and paragraphs. I have an idea about how I’ll build this into a neat little system. But more on that later.
Thinking on paper here, each page when rendered is going to need:
- A
<!doctype>declaration - A
<head> - A
<body>
So let’s hack that out real quick:
<!DOCTYPE HTML>
<html>
<head>
</head>
<body>
content goes here
</body>
</html>Nothing fancy. Let’s add the stylesheet link, as suggested by the Tufte CSS README:
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" href="/assets/stylesheets/tufte.css"/>
</head>
<body>
content goes here
</body>
</html>Let’s make the beginning of the file like this:
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" href="/assets/stylesheets/tufte.css"/>
</head>
<body>and put it in a file called head.html;
λ cat << EOF > head.html
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" href="/assets/stylesheets/tufte.css"/>
</head>
<body>
EOFThen we can take the bottom half of our html file and put it in tail.html;
λ cat << EOF > tail.html
</body>
</html>
EOFAnd that gets us:
λ bat head.html tail.html
───────┬────────────────────────────────────────────────────────────────────────────────
│ File: head.html
───────┼────────────────────────────────────────────────────────────────────────────────
1 │ <!DOCTYPE HTML>
2 │ <html>
3 │ <head>
4 │ <link rel="stylesheet" href="/assets/stylesheets/tufte.css"/>
5 │ </head>
6 │
7 │ <body>
───────┴────────────────────────────────────────────────────────────────────────────────
───────┬────────────────────────────────────────────────────────────────────────────────
│ File: tail.html
───────┼────────────────────────────────────────────────────────────────────────────────
1 │ </body>
2 │ </html>
───────┴────────────────────────────────────────────────────────────────────────────────This is nice because it will let us do things like
λ cat head.html third_render.html tail.html
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" href="/assets/stylesheets/tufte.css"/>
</head>
<body>
<h1 id="bootstrapping-a-blog">Bootstrapping a Blog</h1>
<h2 id="november-18th-2022">November 18th 2022</h2>
<p>I’ve tried many static site generators in my time. They all have far too many opinions. I decided to build the most minimal possible blog I could. I’m writing this article as I build the blog and deploy it. In real time. So as I write this paragraph. The blog doesn’t exist. This will be the first post on the blog.</p>
...
<pre><code>λ pandoc making_a_static_blog_from_scratch.md -o second_render.html
λ open second_render.html
</code></pre>
<p>Aha! Now! Lo and Behold: <img src="/assets/images/second_render_image.png" alt="a screenshot of the second render" /></p>
</body>
</html>Which gets our entire file.
We can get our fourth iteration of the page with:
cat head.html third_render.html tail.html > fourth_render.htmlAnd now we have style: 
However we haven’t updated the site content in a while so let’s produce a more current version of our fourth iteration by running:
λ pandoc making_a_static_blog_from_scratch.md -o fourth_iteration_content.html
λ cat head.html fourth_iteration_content.html tail.html > fourth_render.html
λ open fourth_render.html
(An astute reader will notice I had swapped the parens and square braces for the previous image. That is now fixed.)
Automating

This whole process of running pandoc and concatenating files is starting to get a little tedious. It would be nice to have a script we could use like this:
cat blog_post.md | ./publish > blog_post.html && open blog_post.htmlSo let’s write it. This one is pretty simple so I’ll make it a little shell script:
#!/bin/sh
cat - | pandoc -f markdown -t html | cat head.html - tail.htmlThis reads from stdin, hands it to pandoc with instructions to convert markdown to html, and then concatenates the head.html with stdin (which is now stdout from pandoc) and tail.html.
Make it executable:
λ chmod u+x publishAnd test it:
λ cat making_a_static_blog_from_scratch.md | ./publish > render_five.html && open render_five.htmlWhich works fine:

Define a quick alias and we won’t have to think about it much anymore:
alias publish='cat making_a_static_blog_from_scratch.md | ./publish > current.html && open current.html'Actually, better yet throw that into a Makefile:
publish_blog:
@echo "Publishing..."
@cat making_a_static_blog_from_scratch.md | pandoc -f markdown -t html | cat head.html - tail.html > current.html && open current.htmlAnd now an invocation of make will pop open the most recent version of the post in my browser. I’d like to do batches of posts eventually and additional makefile commands will be useful as the site becomes more sophisitcated. But we’ll cross that bridge when we get there.
Custom Styling
Now, looking at the page I can already see a few styling nitpicks I’m going to have with the default pandoc/tufte generated output:
- Lists (like the one I’m making now) don’t turn into
<li>elements, they just get shunted into the html file in plain text. Not ideal - Code blocks lack syntax highlighting, everything is the same color and it blends together a bit
- No way of representing margin notes in markdown. I’m honestly fine tweaking the html itself in the early stages but it would be nice to automate that bit
- Large images are too wide, they exceed the width of the column of text
- Single line code blocks are obscured by the horizontal scrollbar, and they don’t scroll until the end of the line
- Blockquotes lack a distinct visual style and tend to blend into the rest of the content
Luckily since every piece of style here is under my control we can fix these. Let’s start with the easy stuff first:
Lists
An quick google search leads me to this stackoverflow question, which is nice since I’m not the only one to have this issue. It seems the markdown parser in pandoc requires lists to be separated by a newline between the paragraph and the list. That seems a bit draconian, I’ll try using the commonmark format as suggested by the answer to that question:
#!/bin/sh
cat - | pandoc -f commonmark -t html | cat head.html - tail.html
…and it works! Lists now follow the law of least astonishment. Commonmark spec for reference
But is it worth it? After some finagling and looking around at different features I find I like the <figcaption> tags generated by the regular markdown parser. I’ll stick with that and remember to include a newline between lists items and the preceding line. You win some, you lose some.
Image Width

This one was easy but involved delving a bit more deeply into the tufte css file. Most of the layout styling is applied to elements which are children of a semantic <section> tag, and the default tufte handling of images is a much more sane approach. Sure enough, adding <article> and <section> tags to head.html applies the sane styling to images:
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" href="/assets/stylesheets/post.css"/>
</head>
<body>
<article>
<section>This of course means each <article> has only one <section>, but it works well enough for now so I’ll leave it.
Syntax Highlighting
I went down several rabbit holes here. Starting with pure CSS libraries, moving into random command line tools, contemplating giving up my goal of using zero Javascript for this site. Pandoc though, continues to impress me. Examining the output of any code block generated reveals that pandoc parses the code and puts individual terms into <span> tags with a set of pre-defined classes. Where are those classes defined you ask? Well, it depends. Pandoc syntax highlighting relies on another haskell library called skylight to generate colorized output in pretty nearly any form you choose (HTML, ePub, PDF etc.). This is amazing! And, if I were using the handy -a option pandoc provides, it would generate the needed stylesheet and insert the classes into a <style> tag in the head of the document.
As mentioned before, I’m using pandoc so far to create only fragments of HTML and not complete documents. After discovering the syntax highlighting feature I seriously considered using pandoc as a complete solution. But truth be told I’d like to have as much control over the HTML as I can. So I’ll stick with my simple (if unorthodox) cat solution for the time being.
However, I’d still love the syntax highlighting capability for <code> blocks. Pandoc by default will annotate code blocks and their content with various classnames:
```ruby
def foo
puts "bar"
end
```Becomes:
<div class="sourceCode" id="cb1">
<pre class="sourceCode ruby">
<code class="sourceCode ruby">
<a class="sourceLine" id="cb1-1" title="1"><span class="kw">def</span> foo </a>
<a class="sourceLine" id="cb1-2" title="2"> puts <span class="st">"bar"</span></a>
<a class="sourceLine" id="cb1-3" title="3"><span class="kw">end</span></a>
</code>
</pre>
</div>As you can see, pandoc annotates each token in the language generically and lets us apply specific styles later. All we need to do is provide the classes that represent keywords and strings etc. I looked into using skylight directly to generate the needed CSS, but it looks like the actual HTML/CSS generation is hidden within a Haskell library and the command line version won’t generate pure css. Fortunately we can cheat a little.
As this stackoverflow question points out, it’s possible to dump the raw theme file (which is just json), modify it, and then tell pandoc to use that theme. I’d like to make my code blocks match my terminal theme and editor color customizations as closely as possible. For now I’ll just copy the default theme generated by pandoc --print-highlight-style breezedark > breezedark.theme and tweak it to my liking.
λ bat breezedark.theme
───────┬────────────────────────────────────────────────────────────────────────────────
│ File: breezedark.theme
───────┼────────────────────────────────────────────────────────────────────────────────
1 │ {
2 │ "text-color": "#cfcfc2",
3 │ "background-color": "#232629",
4 │ "line-number-color": "#7a7c7d",
5 │ "line-number-background-color": "#232629",
6 │ "text-styles": {
7 │ "Other": {
8 │ "text-color": "#27ae60",
9 │ "background-color": null,
10 │ "bold": false,
11 │ "italic": false,
12 │ "underline": false
13 │ },
14 │ "Attribute": {
15 │ "text-color": "#2980b9",
16 │ "background-color": null,
17 │ "bold": false,
18 │ "italic": false,
19 │ "underline": false
20 │ },
21 │ "SpecialString": {
22 │ "text-color": "#da4453",
23 │ "background-color": null,
24 │ "bold": false,
25 │ "italic": false,
...I’ll leave the default color options for syntax highlighting alone for now and set the background and foreground colors to #0629435c and #deb88d respectively.
{
"text-color": "#deb88d",
"background-color": "#0629435c",
"line-number-color": "#deb88d",
"line-number-background-color": "#0629435c",
"text-styles": {
...Then take an arbitrary markdown file with a code block and convert it to HTML with our custom theme:
λ pandoc -s foo.md --highlight-style=custom.theme -o foo.htmlAfter a few other tweaks we get something like:

Now we can slurp up the css from our generated file:

And move it into our assets/stylesheets directory.
It’s time to get organized (but not too much) with our stylesheets so I’ll create a new post.css file for post content and include both the existing tufte.css in it:
@import url('./tufte.css');
@import url('./syntax_highlight.css');And in head.html update the stylesheet to reference the new post file:
<link rel="stylesheet" href="/assets/stylesheets/post.css"/>Time to test it:

Boom.
Some of the default styling needs work, I’ll probably keep nearly everything from Tufte as far as layout of code blocks and then let the pandoc/skylight styling do the rest with some modifications in highlights as they come up.
Margin Notes
Proper margin and footnotes in Tufte follow this pattern:
<p>
One of the most distinctive features of Tufte’s style is his extensive use of sidenotes.
<label for="sn-extensive-use-of-sidenotes" class="margin-toggle sidenote-number"></label>
<input type="checkbox" id="sn-extensive-use-of-sidenotes" class="margin-toggle">
<span class="sidenote">This is a sidenote.</span>
Sidenotes are like footnotes, except they don’t force the reader to jump their eye to the bottom of the page, but instead display off to the side in the margin.
</p>I don’t want to have to type all that out by hand every time I want to make a note. Preferably I’d be able use syntax like what pandoc recognizes for notes and have the process be automatic.
I’ll try out pandoc sidenote.
λ cat making_a_static_blog_from_scratch.md | pandoc --filter pandoc-sidenote -f markdown -t html | cat head.html - tail.html > current.html
pandoc-sidenote: Error in $: Incompatible API versions: encoded with [1,17,5,4] but attempted to decode with [1,22].
CallStack (from HasCallStack):
error, called at src/Text/Pandoc/JSON.hs:107:64 in pandoc-types-1.22-Bw7urHogZSoFhJ20EcUJPg:Text.Pandoc.JSON
Error running filter pandoc-sidenote:
Filter returned error status 1Well that’s no fun. It’s worth noting that I installed pandoc some time ago and sure enough:
λ pandoc --version
pandoc 2.5Which according to the documentation is not a supported version. I think I’ll take a risk and blow pandoc away, and install the latest which by my count is 2.19. It makes me nervous this late in the game, but hey, what’s the worst that could happen?
λ brew link --overwrite pandoc
Linking /usr/local/Cellar/pandoc/2.19.2... 3 symlinks created.
λ pandoc --version
pandoc 2.19.2I was really hoping to humorously tempt fate when I wrote that, but trying the command runs successfully. That’s nice except that there are no actual sidenotes or marginnotes in this document.
Let’s add one: No time like the present
Trying the command runs successfully. That's nice except that there are no actual sidenotes or marginnotes in this document.
Let's add one: [^1]
[^1]: No time like the presentWith any luck the previous sentence before the code block will now render with a Tufte style sidenote. Running the command again produces:

No dice.
After some finagling, I found that one needs to specify the footnotes extension as part of the markdown format to pandoc:
pandoc --filter pandoc-sidenote -f markdown+footnotes -t htmlOnce that’s in place, it goes smoothly:

Just like magic. Kudos to markdown and pandoc-sidenotes. Sorry-not-sorry if the reverse time recursive image thing is getting a little out of hand.
Scrollbar issues with <code> blocks
I’m not in love with the current appearance of the horizontal scrollbars:

Fortunately MDN provides a description of a css attribute scrollbar-color. Which allows you to style the scrollbar no matter what random redditors have to say:
div.sourceCode {
...
scrollbar-color: #ffffff17 #fff0;
}This works fine, except on webkit browsers, so to get that working we’ll need:
.sourceCode::-webkit-scrollbar-track {
background: #fff0;
}
.sourceCode::-webkit-scrollbar-thumb {
background-color:#ffffff17;
}Blockquotes
Here’s an example of a blockquote:
Sometimes it’s better to light a flamethrower than curse the darkness.
– Terry Pratchett
As I write this paragraph, that blockquote looks like this:

It’s not awful, but it’s not excellent either.
A few things that could make it better:
- Font styling
- Background color
- Left border
- The ability to control where newlines appear
Let’s work on adding those. Here’s what the css looks like currently:
blockquote {
font-size: 1.4rem;
}
blockquote p {
width: 55%;
margin-right: 40px;
}First off, I’d like the background color to be slightly different. However since the blockquote element takes up the full content of the <section>, I think I’ll swap the width of the blockquote p with the blockquote, and let the child <p> always take up 100% of its parent <blockquote>:
blockquote {
font-size: 1.4rem;
width: 55%;
}
blockquote p {
width: 100%;
margin-right: 40px;
}Once that’s taken care of all that remains is to set up some background color and padding:
blockquote {
font-size: 1.4rem;
background: #5B5B662E;
padding: 2px;
width: 55%;
padding-right: 15px;
margin-left: 0px;
}
blockquote p {
width: 100%;
margin-right: 40px;
padding-left: 25px;
}Additionally, I would like a font that’s easily distinguishable from the rest of the text, and a border to the left:
blockquote {
font-size: 1.4rem;
background: #5B5B662E;
padding: 2px;
border-left: 10px #db9d59 solid;
width: 55%;
padding-right: 15px;
margin-left: 0px;
}
blockquote p {
width: 100%;
margin-right: 40px;
padding-left: 25px;
font-size: 1.5rem;
font-style: italic;
font-family: courier;
}Now here’s what the blockquote looks like:

Better. I would still like the -- Terry Pratchett bit to appear with a newline between the quote body though.
I’m not sure there’s a way to get the markdown to work that way honestly. Pandoc removes the implied newline completely, which is in line with markdown spec if I recall. Here’s what it is now:
> Sometimes it's better to light a flamethrower than curse the darkness.
> -- Terry PratchettPlaying around with it for a moment I find that I can get it to behave the way I want if I include a non-emptyNote the single space character on the “blank” line
line between the two:
> Sometimes it's better to light a flamethrower than curse the darkness.
>
> -- Terry PratchettHere’s the final result for the blockquote:

I like this for big callouts to a piece of text. However I would prefer smaller callouts to be available. For now I suspect I will simply italicize those and leave them in italics.
The End?
In short, no. Though much has been accomplished:
Lists don’t turn into=> Include a newline between the list and the previous line, or use commonmark<li>elementsCode blocks lack syntax highlighting=> Solved with pandoc extensionNo way of representing margin notes in markdown.=> Solved with pandoc extenstionLarge images are too wide=> Structure the page the way tufte css expectsCode block scrollbar issues=> Use thescrollbar-colorcss attribute and some pseudo-classes to styleBlockquotes need more distinct styling=> Done with the above customizations.
There are various other changes I made as I went along that I don’t feel the need to document here, both in terms of text and style. If you’re curious, you can always pull up the site inspector yourself. After all, the map is not the territory.
It is at this point that I will stop documenting styling and layout changes to posts. It has been a useful excercise, and I suspect that the only reason I’ve come this far is because I’ve treated the development process as a way to both code and write at the same time. After all, the two things are not so different when done properly.
Content styling is complete. The remainder has yet to be built.
There are more things in heaven and earth, Horatio,
Than are dreamt of in your philosophy.