Building a Static Site Generator: The MVP

Building a Static Site Generator: The MVP
Photo by Michael Dziedzic / Unsplash

I'm building a markdown-based static site generator with robust but simple templating and extensibility. All of the power of a vanilla HTML page but easy content authoring in markdown. There are lots of options for markdown-based sites and blogs; however, I've never built anything like this from scratch and wanted to learn more about building this type of application.

The hardest part, as with most projects, is finding a good name and domain. After finding that everything I (and ChatGPT) thought of was already a thing I landed on static but with the Ts and Cs swapped. From that, scacit.com was born. Good enough name and an available .com.

Architecture

My primary goal with building this is learning how it's done. That's impacted my decisions thus far. One drawback to this approach is that I'm doing some stuff manually that would probably be higher quality if I used an existing solution.

  • Langauge: I want users to be able to create, develop, and build the site using a npx script based CLI, so I'm using Node.js with TypeScript, a combo I'm already very familiar with.
  • Markdown: I love the experience of writing in markdown (a big reason why I like Notion's markdown-like editor). I find it to be much faster than rich-text Word-style editors with complex button-based formatting. I also want more than just basic markdown capabilities, such as syntax highlighting for code and callouts. I may revisit this in the future, but for now, I'm using Markdown It, which provides easy rendering and extensibility with plugins to add common enhancements.
  • Code Blocks: For code syntax highlighting, I went with highlight.js, which has an easy-to-use Markdown It plugin.
  • Metadata: Inspired by Dev.to's front matter usage, I decided to use front-matter to extract metadata information from content markdown pages. I like this solution since it keeps a piece of content's information centralized within the file rather than needing data JSON files or something.

That's the general gist of the external packages I'm using. For the main rendering engine, I'm rolling my own. I made this decision because I think this is where I can most learn.

For my rendering engine, I want to support several key features:

  • Variable insertion
  • HTML components (PHP-style where the component, an HTML file, is included in the page)
  • If conditionals to conditionally render content
  • Loops to be able to show lists of posts and create a sitemap

The syntax I chose is double-angeled brackets. So, for example {{ site.title}} would on this site insert CRTV Dev. I think this looks really clean, is clearly an insertion tag, and isn't awkward to type. So far, variables and components work great, but ifs are buggy and loops don't work.

Progress

So far, I've been able to make pretty good progress. Below, you'll see the demo post that showcases the available markdown syntaxes:

Hello World Post

So far, to create a site, you run npx @mackenly/scacit init to scaffold the necessary files to create your content. It will generate something like this:

  • assets/
  • content/
  • components/
  • layout.html
  • scacit.config.ts

The config file lets you set site and build variables and since it's a JavaScript file rather than JSON, you can create variable values with JavaScript that will be evaluated at build time. The assets directory has a CSS and JS file that gets loaded on every page. The components directory is where component HMTL files are defined. Currently, the layout.html file is like the theme for the site and is used on every page to create the HTML structure of the site. Finally, the content directory contains markdown files for each post/page (with index.md being the home page).

To develop the site, users can run npx @mackenly/scacit dev which builds the site and starts an express server to run the site locally. When changes are made to the site, it rebuilds (requiring you to refresh the page, for now).

When ready to go to production, run npx @mackenly/scacit build which will build out your site. From there, you can host the static file anywhere that supports static HTML/JS/CSS sites. You could even use an S3 bucket.

Next Steps

  • Support nested file-based routing
  • An error/404 page.
  • Different layouts for different routes.
  • Better testing—a lot of the templating engine control structures are not behaving as expected consistently.
  • Built-in image optimization.
  • Hot reloads.
  • CI/CD action templates.
  • Documentation site (built with Scacit, maybe?).
  • Exposing a list of content available to be used in loops.
  • Maybe some kind of site search?
  • More themes, such as a dedicated dark and docs theme.
  • Variable transformations using a yet-to-be-determined syntax, such as for capitalizing the first letter, title case, a fallback value, etc.
  • Experiment with getting content from an external headless CMS.

There's still a lot to do, but it's all very attainable, and I'm looking forward to making this into a legitimately useful tool that's really easy to customize. If you need to build a static markdown-based site for a blog or docs, this might be a good option or at least a starting place (it is MIT). You don't always need to reach for a heavy-duty framework or CMS. Sometimes, using something that you fully understand and can trim down to just what you need gives you a cheaper, more performant, and easier solution.

If you want to follow along, make sure to star the repo: https://github.com/mackenly/scacit and sign up below to get emails about future progress (and other posts from this blog):