# Static site generator

## What it is

A static site generator is a build tool that takes a folder of source files,
usually pages written in Markdown and a set of templates, and turns them into a
finished website made only of files: HTML, stylesheets, scripts and images that
a web server sends exactly as they are. The term is often shortened to SSG, and
“static” describes how the result is served, the same bytes to everyone who asks
for an address, not how a page behaves once it reaches a browser.

It sits opposite the dynamic approach of a content management system or a web
application, where a program on the server assembles each page when it is
requested, usually by querying a database and filling in templates. A generator
runs the same kind of templates over the same kind of content, but once per
change instead of once per visit, and leaves the result in a folder that an
ordinary web server, a storage bucket or a content delivery network can serve
without knowing how the pages were made.

Hugo is one such generator. Its documentation introduces it as “a static site
generator written in Go, optimized for speed and designed for flexibility” that
“renders a complete site in seconds, often less”. The usual loop is to write
with its embedded web server running, which rebuilds on every change and reloads
the open browser pages, then build the site and deploy it to a host:

```sh
hugo server     # build, serve, rebuild and reload on every save
hugo --minify   # write the finished site into public/
```

## Why it matters

The choice is about when the work happens. A generator moves it from request
time to build time, and almost everything that follows comes from that.

Sending a file is about the cheapest thing a web server does, so a generated
site answers quickly and can run on a very small machine, and because every
visitor to an address gets the same bytes, caching is simple. With no
application code or database running behind the pages, there is far less to
secure and patch. The output is a folder, so moving to another host means
copying files, and the source lives in version control, where every change to
every page can be reviewed and traced.

The costs come from the same place. Every change, a corrected typo included,
means a fresh build and a deploy, so build speed matters more as the page count
grows. Anything that depends on who is asking, such as search, comments, sign-in
or a form that stores what it receives, needs code in the browser, a separate
service, or a way to be settled at build time, because there is no program on
the server to do it. Time stands still between builds: Hugo by default leaves
out a page whose date lies in the future, so a page scheduled for Monday appears
with the first build on or after Monday. Even redirects change shape. With no
program to send an HTTP redirect, Hugo's default for a moved page is a small
HTML file at the old address that sends the browser on, an approach its
documentation calls portable across all hosting providers.

## What it looks like in practice

This site is built with one. [The credits page](/credits/) names Hugo as the
static site generator and credits it with the templates, the taxonomies, every
language and the feeds. Of what reaches a browser, it says the HTML is written
out in full before anyone asks for it: every page is a file on disk, not a
template assembled per request. Even the look is settled at build time, since
the light theme's wall is drawn while the page is built and the site is dressed
afresh each time it is published. The quality gate runs locally, on the
computer the site is edited on, without a hosted build service anywhere in the
chain; it treats every diagnostic as an error, and in October 2026 the site built 2,268 pages.
They are served from a single-core server with 512 MB of memory, where the web
server and the three other services that answer for the site read the same
folder of files, so one deploy updates all four.

[The portfolio entry on building this site in Hugo](/portfolio/built-a-multilingual-static-site-in-hugo-126/)
gives the reasoning. Three languages, nothing to pay to run it and no visitors
handed to anybody ruled out almost every content management system, because a
backend that runs is something to secure, patch, pay for and explain on a
privacy page. The site has 108 template files, 53 of them partials, publishes
every page in four representations from one content tree, and builds that tree
seven more times for focused subdomains by pointing the generator at a different
content directory. Capability is added at build time rather than in the browser,
at a price: everything interactive has to be solved at build time or not at
all, which is why a search index was costed and parked rather than shipped.

## Where it goes wrong

**A deleted page stays online.** A page removed from the source is still served
at its old address, and search engines go on finding it. Hugo does not clear its
output folder before a build: existing files are overwritten but never deleted,
so `public/` can keep pages from earlier builds, drafts and expired pages among
them, and anything that copies that folder to a server copies them too. Its
`resources/` cache has a cousin of the problem: this site's design rules warn
that it can serve a stale CSS bundle when only the source CSS has changed. Build
into a clean folder, with Hugo's `--cleanDestinationDir` flag or a delete step
of your own; this site's build removes both `public/` and `resources/` before it
starts.

**A finished page never appears.** A post is written, committed and deployed,
the build reports success, and the site has no page for it. By default Hugo
leaves out content marked as a draft, content whose date or publish date lies in
the future, and content whose expiry date has passed, and none of that counts as
an error. A date typed a day ahead is enough. Check those four fields first,
keep `--buildDrafts`, `--buildFuture` and `--buildExpired` for previews, and for
a scheduled page arrange a build after its date.

**Every page claims to have changed at once.** The sitemap gives nearly every
address the same last-modified time, which tells a crawler the whole site
changed and says nothing about which pages did. A generator has to take each
page's modification date from somewhere, and the file's own timestamp is the
wrong source when a step rewrites files whose content stays the same. This
site's configuration records that its sync step rewrites all 189 content files
on every run, so 172 of the 176 sitemap addresses shared one timestamp. Take the
date from version control instead: Hugo reads the date of a file's last Git
revision when `enableGitInfo` is on, and a re-sync that produces identical bytes
makes no commit, so a re-sync after a month of edits touched 8 of 186 files.

**Files nobody meant to publish go out.** Anyone who guesses the name can fetch
a file that lists other files. A generator publishes whatever sits in the
folders it reads, minus what an exclusion rule removes, and a rule that does not
reach every folder leaves the rest exposed. This site's configuration records
that its site-wide ignore list did not reach a module mount: the build published
all six `.DS_Store` files the macOS Finder had left under icons/, images/ and
webmanifest/, on every host, and each listed the names beside it, so the one in
images/ named a folder the site withholds. A filter missing one space after its
`!` once excluded every image in a mount instead. Exclude on every mount that
publishes, then check the built output for files that do not belong; two of this
site's checks fail a build that serves one.

## Further reading

- [Hugo: Introduction](https://gohugo.io/about/introduction/) — what Hugo is,
  what it is used for, and how its write, build and deploy loop runs.
- [Hugo: Basic usage](https://gohugo.io/getting-started/usage/) — building and
  serving a site, why `public/` keeps old files, and which content a build
  leaves out.
- [Hugo: Configure front matter](https://gohugo.io/configuration/front-matter/)
  — how Hugo picks a page's dates, including the `:git` and `:fileModTime`
  tokens.
- [Hugo: Configure modules](https://gohugo.io/configuration/module/) — mounts,
  the `files` filter each one takes, and the rule that defining a mount removes
  the default one.
- [Hugo: URL management](https://gohugo.io/content-management/urls/) — aliases,
  and the redirect files a static host serves in place of a server redirect.

<https://engineer.company/glossary/ssg/>
