Home
| Colors: |
March 2026

Guidelines for Building a Static Site Generator (2026 Edition)

The Next Generation

I've built several static site generators for personal use. Early ones were to support neopolitan. Later ones were to make it easier to build sites in general.

Now, I'm looking to move my site from alanwsmith.com to al9000.com. Part of the reason I want to make the move is to pull all my sub-domains back under the main site. It'll make things a lot easier to link back and forth and to update with global templates.

I also want to expand the site by adding in my Rust Grimoire. I've got tons of rust notes on the site already, but the grimoire isn't included. Examples in it span multiple files that my current site generator isn't set up to accommodate.

TODOish

The new generator is just for me. No need to worry about it making sense for other folks. So, I'll mostly be building things as need arises. That said, there are some basics that I want to make sure I've thought through before kicking off. Here's the list (which I'll add to whenever I think of something).

  • Split out the neopolitan parser so that it's its own thing that can be included in other projects.
  • Use MiniJinja as the template engine (it's Jinja, but in rust).
  • Every page on the site gets loaded as a template that can be included by any other page.
  • Make a two pass system so that each page can be rendered and then the rendered version be included in the next pass.
  • Standard tokes that are processed on the first pass are:

    • [! !] functions
    • [@ @] output
    • [# #] comments
  • Tokens for the second pass are:

    • [2! !2] functions
    • [2@ @2] output
    • [2# #2] comments

    During the first pass those tokens get changed to their standard counterparts so they get used normally in the second pass.

    NOTE: it may more more sense to do [1@ @1] etc to assist with caching. The idea being that you can watch files for changes and only update the ones with [1@ @1] on the first run through and everything else could be cached and ready. (Not sure if that makes sense or not. Something to look into)

  • Escaping tokens is done with:

    • [/! !/] functions
    • [/@ @/] output
    • [/# #/] comments

    After the second pass those tokens are converted to strings of their non-escaped versions.

  • Template names are the full path to the file (as opposed to one of my current site builders where the leading / isn't there in the template names)
  • Syntax highlighting of blocks. (Return values won't include and pre tags, just the spans to put into a pre tag).

    Ideally this can be [! hlblock("html") !][! endhlblock !]

    But I think i may have to be [! filter hlblock("html") !][! endfilter !]

  • Syntax highlighting of spans.

    [! filter hlspan("html") !][! endfilter !]

    As well as:

    [@ hlspan("html", "the string to highlight") @]

  • Syntax highlighting of files [@ hlfile(PATH, "html") @].
  • Funciton to pull specific lines from a file with syntax highlighting applied (i.e. the syntax highlighting happens on the entire file and then the lines are pulled)
  • Cache syntax highlighting since it can take some time.
  • Use tracing library to be able to meausre performance when necessary.
  • Shorthand for [! filter some_filter !][! endfilter !] that is [! f some_filter !][! endf !].
  • [! md !][! endmd !] filter.
  • Actually, thinking about it, you should be able to use whatever strings you want for filters and then just find/replace them with whatever minijinjas needs (e.g. [! md !] becomes [! filter md !]).
  • Include a full file as markdown (e.g. [@ mdfile(PATH) @])
  • Good error messages for where things went wrong in template processing or neopolitan parsing.
  • Update the most recently changed file first and send refresh to browser when its updated.
  • Auto populate `/last-update/index.html` with whatever the most recent .html file to have changed is.
  • Any valid JSON files is available from a [@ json @] variable (e.g. /some/data.json is available at [@ json.some.data.KEYS @]
  • Hooks to run before the build, after the first pass, and after the second pass.
  • Provide JS minifier
  • Provide CSS minifier
  • Auto genreate RSS feeds. One for all posts. One each for different tags.
  • Auto generate pages for each tag (with pagination)
  • Auto generate index link pages for posts (with pagination)
  • Provide details on the time it takes to render each page.

Neopolitan

  • Need to do more thinking about how to integration Neopllitan and MiniJinja.
  • I think the biggest thing will be setting up the -- template in page metadata to define the minijinja template to include from.
  • There will have to be some things that build out a little differently. I think it's basically going to require a preprocessor that turns neopolitan files into base level HTML files that are ready to be processed by minijinja.
  • More thinking (and, more importantly, prototyping) to figure that out.

Images

  • A single image folder that can have any folder/file structure inside it.
  • Image calls in content are done with just the image file name without the extension. The engine finds the right file and links it up based on the name.
  • Auto generate responsive image sites.
  • Auto generate responsive image tags with the available sizes.
  • Output files are generated in the same places as their inputs. That is, /path/filename.html does not become /path/filename/index.html.
  • File extension can change (e.g. "file-name.neo" becomes "file-name.html").
  • Any file can have a `.off` added before its extension to opt it out of converting tag

File Metadata

During processing every file has access to a filemd variable that holds metadata including:

  • File name with extension.
  • File name without extension.
  • The source file extension.
  • The destination file extension.
  • The full string path to the source file.
  • The string path to the parent folder.
  • An array containing strings for the path to the parent folder split on each directory level.
  • A variable that points to the files directory for calling JSON in it. (e.g. /path/to/file.html and /path/to/data.json would have a variable called [@ local @] that is an alias for `[@ json.path.to @]so you can do [@ local.data.KEY @] to get whatever is in the data.json file.

Folder Metadata

  • Similar to file metadata
  • details tbd

Site Metadata and Functions

  • A folders() function that returns an array of all the folders on the site where each entry is itself an array of the folder path split by directories.
  • An exists(PATH) that returns true if a given path exists.

Miscellaneous

  • Build in preview server with hot realoading on file changes (with appropriate throttle/debounce).
  • Files and directories starting with two underscores (i.e. __) are available to be used as templates but don't get transformed/output directly.
  • files_in_folder(FOLDER_PATH) function
  • files_in_folder_ext(FOLDER_PATH, EXTENSION) function (i.e. files_in_folder_ext("/some/folder", "jpg") finds all the JPG files. EXTENSION can also be an array to find files with any number of extensions.
  • folders_in_folder(FOLDER_PATH) function
  • path(STRING, STRING...) function that assembles strings into safe paths.

Version 9000

That's everything I can think of at the moment. That thinking included taking a look at my current generators and this prior post with earlier ideas. Some of that holds up. A lot I'm thinking about differently these days.

I've got bitty in pretty good shape. Moving into this new generator isn't too far away.

Pretty excited about the update, tbh,

-a

end of line