Filip Hráček / text /
You may be familiar with the recent trend of “Indie Small Web”. It’s the idea that, if you have valuable content worth sharing, you put it on your own bespoke website instead of feeding it to a huge media corporation for them to monetize it.
For example, if you have insightful commentary on something in your area of expertise, instead of writing a Facebook post, you put it on your own website. If you create an online course, you don’t put it on Udemy or YouTube, you put it on your own website. And so on.
Now, this assumes you have a website, or that you are able to build it. This obviously gives “Indie Small Web” a bit of a barrier to entry. That said, it was at least as hard to make personal websites 20 years ago — before all of the Twitters and Facebooks and YouTubes came to dominate the web by making it easier to share content — and yet personal websites did exist back then.
I've made a bunch of small websites and pages in the past several years. Here’s a sampling:
All of these are static. The servers they’re running on do nothing other than serving HTML files and associated resources (such as images). There’s no dynamic functionality to any of these.
This is important because of a few reasons:
When I’m making a website, it’s very rare that I ever miss dynamism. In the past, I’d want some way for people to leave a comment. But these days, there are so many better ways to discuss that this use case for dynamic sites is obsolete.
Oftentimes, I literally just write the content in HTML by hand. HTML was meant to be authored by humans, and although it’s not ideal by any means, it’s quite possible to just write in HTML and have a nice-looking result in reasonable time. A good editor can help with the boilerplate.
But there’s a catch. Once you’re writing more than one page, you’ll inevitably start repeating yourself. As a developer, it feels dirty to have so many copies of basically the same code all over the place. It’s not “DRY” and I constantly fear that I’ll fix a problem in one file but will forget to update another one.
Wouldn’t it be great to have some kind of system that lets me define templates like this ...
<!DOCTYPE html>
<html lang=“en”>
<head>
<meta charset=“UTF-8”>
<title>$TITLE</title>
...
... and get a bunch of HTML (and JSON and XML) files?
The obvious answer to this problem is to use a static site generator with a templating language. There’s Jekyll and Liquid, Hugo and Go templates, Gatsby and JSX. There are “standalone” templating languages like Mustache that you can easily use in a number of static site generators, including custom ones.
If you truly need something like Jekyll or Hugo, then by all means, use it. But my experience has been that these technologies are really only worth it above certain complexity threshold.
For the cases where you need structured data in .yaml
files, something like Jekyll is fantastic, and well worth the additional complexity and longer build times.
But when I look at my “Indie Small Web” projects, the vast majority of them don’t need this.
All I need is:
That’s it.
m4
Looking at the other end of the complexity spectrum, one can choose m4
. This is a tiny macro processor that is easy to install on Unix-based systems and has been part of the Unix ecosystem since 1977 (written by K&R, no less). It does text substitution and file inclusion, so it checks 2 of the three requirements above. Conversion to Markdown could be done in a separate step, with another command line tool.
I went this route for a bit, but m4
suffers from the usual ailments of ancient Unix tools. Its syntax is sometimes delightfully simple but other times, it’s arcane.
Do you want to avoid printing newlines after directives? Use divert(-1)dnl
but don’t forget to divert(0)dnl
once you’re ready to output some lines again.
How do you express string literals in m4
? The starting character is a backtick, and the closing one is a single quote:
`I am not kidding.'
What’s the syntax of expanding a macro? You literally just include the exact string of the macro name for it to register.
define(`OP', `Filip Hráček')dnl
The full name of the original poster is OP.
But beware, the macro will expand anywhere. OOPS!
After a while of using m4
, I saw so many potential foot-guns that I decided to look elsewhere.
Back in the 1993, before PHP, Ruby or Java, there was this super simple templating language for servers called Server Side Includes (SSI). It had only a few simple directives that were hard to miss even in old editors without syntax highlighting. For example:
<!DOCTYPE html>
<html lang=“en”>
<head>
<meta charset=“UTF-8”>
<title><!--#echo var=“TITLE” --></title>
</head>
<body>
<!--#include file=“content.html” -->
</body>
</html>
Guess what? For a simple static site, this is perfect! You get very obvious syntax that’s hard to overlook. You get an easy way to include other files. And since the semantics are so limited, there’s less chance you’ll over-engineer your tiny little website.
It’s also a long-lived syntax (that’s still supported in servers like nginx and Apache).
I’m gradually converting all my small sites to using SSI.
I want to be absolutely clear about one thing: I’m not using SSI to serve dynamic content. I only run ssi
at build time, and then serve the generated static files.
I also decided to write my own ssi
tool. It’s 221 lines of code in 8 files. It’s naive code, sure, but frankly, I don’t see any immediate need to make it more robust. It does the job and it runs very fast.
My “Indie Small Web” sites tend to be built using a Makefile
. Here, too, I place a premium on simplicity and longevity (make
has survived since 1976 so, according to the Lindy Effect, it’s likely to survive for many more decades to come).
A typical Makefile
for a site can look like this:
.PHONY: build serve install
serve: build
cd web && python3 -m http.server
build:
ssi src/index.definitions.txt src/header.shtml \
src/index.md src/footer.shtml \
> web/index.html
ssi src/other.definitions.txt src/header.shtml \
src/other.md src/footer.shtml \
> web/other.html
# ... more files ...
install:
dart pub global activate --source git https://github.com/filiph/ssi --git-ref main
(My ssi
tool takes inspiration from m4
in that it concatenates the input files given to it. So you can have variable declarations in index.definitions.txt
, a common header template in header.shtml
, Markdown content in index.md
, and a common footer in footer.shtml
. This is just one way to do this, though. You could have a single index.shtml
file with definitions and include directives — and I often do just that.)
Notice how I don’t even bother with things like Makefile’s source file dependencies. make
just runs the whole build process every time I ask it to, without trying to optimize which files to update and which ones to leave be. This is because we’re talking about small sites, and the tool finishes in about 10 milliseconds per invocation. That means I can have my IDE set up to run make build
on every save of any file in my project, and the response is still immediate — even without dependency graph build optimization.
I wanted to share my experience with building static “Indie Small Web” sites because — generally speaking — blogposts on this topic tend to overcomplicate things. As I said above, it’s probably okay to just write straight HTML, even if you have more than one file. It might be worth it to bring in something like m4
or ssi
if you, like me, have lots of tiny sites. But I wonder how often people really need Jekyll or Firebase or Webpack to get their content out there. I think it’s mostly overkill, and acting as if it’s “the norm” is not helping.
That said, what do I know? The approach has worked for me so far but I have no idea whether it will hold up in the future, and of course I can’t claim it will work as well for anyone other than myself.
My hope is that I get a person or two excited to try building their own little garden on the internet. And, frankly, I don’t really care what technology they’ll use for that.
— Filip Hráček
May 2025