How to make a static website

Making a static website has been a solved problem for a long time, yet people still manage to fuck it up in the most spectacular of ways. Google is not helpful either, since it gives you the next shitty version of Wix.

Let’s start from the top. This is how you make a static website.1

What is a static website, anyways?

A static website is one that serves the same content to everyone. A site like Facebook is not a static website because depending on who you’re signed in as, you’ll have permission to see different things. How that’s done is beyond the scope of this article, though.

This site is a static website. Your personal website (if that is what you are making) will likely be one too.


Before you write a website, you should know how HTML and CSS work. (You do not need to learn how Javascript works, because you should never use it anyway.) Be willing to Google, but know that most resources are counterproductive when it comes to web development.

General principles to keep in mind are avoid Javascript at all costs2, generally don’t trust Medium or articles, and prefer MDN over W3Schools (in some cases, W3Schools is acceptable).


The key idea is that HTML is marked up with tags, some of which contain semantic information. Here’s am example of a valid HTML document:

<!DOCTYPE html>
   <html lang="en">
   		<title>Dennis Chen's Web Page</title>
   		<meta name="viewport" content="width=device-width, initial-scale=1.0">
   		<meta name="description" content="This is a description.">
   			<h1>Dennis Chen's Web Page</h1>
   			<p>Welcome to my webpage!</p>

HTML should be considered a tree: each element can have children, and most elements belong to a parent (the only exceptions are the outermost html tag and the DOCTYPE tag).

Let’s break each of these tags down one by one and explain why all of them are necessary:

You must include all of these tags in your HTML document. If you have a navbar (you probably should), put it inside <nav>. Same for footers: use <footer>.

If you use the right tags, the document will semantically make sense without ARIA roles. (If you don’t know what ARIA tags are, that’s fine. Only Javascript soydevs, including myself once upon a time, who can’t be assed to read the docs or Google will need them anyway.) If you are using ARIA tags, you are probably doing something wrong. Even Mozilla agrees with me:

Many of these widgets were later incorporated into HTML5, and developers should prefer using the correct semantic HTML element over using ARIA, if such an element exists. For instance, native elements have built-in keyboard accessibility, roles and states. However, if you choose to use ARIA, you are responsible for mimicking the equivalent browser behavior in script.

Just don’t use custom form fields, buttons, etc and you will be fine. If you don’t know what I’m talking about: good. Keep it that way. A button should obviously be a <button> and not a <div onclick=>, anyone that tells you otherwise is wrong.


CSS is how documents are styled. Some people have different perspectives on how much of the document should be styled with CSS versus inline with HTML. Should everything be styled in a main.css stylesheet? Or should some styling be done with HTML, a la Tailwind? I am personally of the opinion that most styling belongs in CSS stylesheets, but other perspectives exist and may be worth considering.

CSS is also a total nightmare. Okay, not really, it’s just kind of hard for beginners to implement some useful things. In this section we’re going to discuss how to accomplish some more tricky things, like having a navbar where the “Home” link is on the left and the other stuff is all on the right. (We skip over the easy stuff like changing colors because any idiot can do that.)

The two CSS classes you should know about and are least likely to are flex-boxes and grids. For flex-boxes, you should know that justify controls alignment of elements across the main axis and align controls alignments of elements on the cross axis (perpendicular to the main axis). In conjunction with margin: auto, or perhaps a directional margin-right: auto, the flex-box can be very powerful.

With regards to fonts, prefer to use general font families rather than specific fonts. I know the default sans serif is ugly, so at the very least, fall back to a general font family if you must specify a font.

Here is a rudimentary example of a navbar with a “Home” link left-justified and the other links right-justified.


   	<a class="nav-home" href="/">Home</a>
   	<a href="/link1">Link 1</a>
   	<a href="/link2">Link 2</a>

Note that the class nav-home doesn’t mean anything yet, it’s only when we apply CSS styling that it makes a difference.


a.nav-home {
   	margin-right: auto;

In general, I recommend putting the following directive in your CSS stylesheet:

html {
   	font-family: sans-serif;
   	max-width: 800px;
   	margin: auto;

You can change max-width if you want. What’s important is that, based on whatever fontsize you choose, there are around 60-80 characters in each line of text. Anything more is unreasonable. margin: auto just automatically uses the remaining space and divides it to create equal margins on the left and right, which has the effect of centering it.


If you’re reading this website, you probably have some interest in getting math to render properly on your website. If you’re using pandoc, there are a variety of flag combinations you can pass to get embedded math equations. Unfortunately, each of them have their traedoffs (read: they all suck).

With regards to styling, only MathML works as you’d want it to. KaTeX is slightly hacky, you have to modify the fill and stroke attributes because you’re working with SVGs, and WebTeX is really hacky because you have to invert all images with class math.

I personally use MathML because I don’t have a lot of complex equations to render, that’s not what my writing is usually about. If you must have everyone see your math rendered and must not use Javascript (a rare combination!) then use WebTeX. For everyone else, KaTeX is the best choice. But MathML has made some promising progress, so I’d check back in mid-2023. (That’s when the people spearheading the inclusion of MathML in Chrome hope to have it upstreamed.)


Pandoc also solves the problem of displaying code snippets. Just use code blocks and annotate a language, as thus:

   printf("Hello, world!");

This will get you

printf("Hello, world!");

Pandoc uses Skylighting to semantically mark up each of the characters with an HTML class. Therefore, you can apply a stylesheet for syntax highlighting. See this example stylesheet.

If you want, you can explicitly number lines as such:

```{.c .numberLines}
   printf("Hello, world"!);

However, I anti-recommend this approach. If you’re displaying more than one line of code in a code block, you should always be annotating line numbers if only for consistency. This is what I do on this website.

Pandoc will apply the semantic classes for you, but it is still up to you to apply CSS styling for it. Here’s a shell script I copied off the internet that will give you a CSS stylesheet for your code:

   trap 'rm -f "$tmp"' EXIT
   echo '$highlighting-css$' > "$tmp"
   echo '`test`{.c}' | pandoc --highlight-style=$style --template=$tmp

If you want a list of the valid highlighting styles you can pass into this script, run pandoc --list-highlight-styles.

I’ve made some modifications with regards to spacing in my CSS stylesheet and removed the background, because I think the defaults generated by pandoc kind of suck.

Editability and the build process

Just because you need to serve HTML doesn’t mean you need to write all of it. Typically what a beginner will do is manually write HTML for each page, with some judicious copy-pasting. Instead, the correct approach is to write content in an easy to edit format. The most popular format is Markdown, and it’s what I personally use.

This is where you need to start learning how to the command line. You need to be able to build your website using commands on a Unix-based operating system because that’s where it’ll be hosted. Windows users, take note: This means you need to get off of that shitty operating system, at least for development purposes. Spin up an instance of Linux (I recommend Alpine) using qemu. See Drew Devault’s qemu post for help setting that up.4

The way your build script will look depends largely on the file structure of your website. However, you will almost certainly be using pandoc to convert HTML to Markdown, jq to parse the metadata from your Markdown files, and probably sed/awk to insert your generated HTML into your base templates.


Hosting is largely beyond the scope of this article. However, I will briefly overview the two approaches you can take:

For the latter you need your own domain, and even if you aren’t hosting on your own server, you still can use one as well. You can get one through a variety of domain registries, but make sure that whatever you choose has a good API.


I am a very opinionated developer, so you may find yourself disagreeing with some of my points. However, please make sure your websites are accessible and design with speed, longevity, and empathy in mind. Ultimately your users will not care if you built your site with Hugo or a series of bash scripts, but hopefully this post has convinced you that you do not need or want the latest Javascript framework for a simple, static site that doesn’t need to provide an interactive experience anyway.

I hope this post was helpful! A post on full-stack websites will be coming soon :)

  1. If you would like an example of a good website, see My personal website is a bit more complex, but it shows you how to auto-generate the writing page as well.↩︎

  2. The only exception I can think of is interactive timers, and maybe autosubmit once time runs out, which is all the Javascript I intend to put on the MAT website — speaking of which, one day it will be fixed…

    Not that any of this is relevant to this post, since MAT is not a static website.↩︎

  3. The --self-contained flag generates the images and stores them as base64 in the HTML files, which means that at least you aren’t calling an external website for your images.

    But if you have non-inlined CSS styles, it won’t be self-contained… god, what a fucking mess!↩︎

  4. I don’t recommend using a virtual machine for Linux, however. In the long term you will learn so much more if you just daily-drive it.↩︎

  5. I’ll elaborate a bit on why SourceHut Pages is the correct choice. Basically, it’s because you can “push” to SourceHut pages by just sending a tarball instead of using a Git repository as an extra layer of indirection.

    This makes it really easy to make publishing just a post-push Git hook.↩︎