.NET SSG Technology Choices
First, let us consider what SSG frameworks are available for .NET. If you head over to the Jamstack.org generators page and filter the list by .NET you will find that there are a few static site generation frameworks available for .NET!
- Wyam (former Statiq now depricated)
- Pretzel (deprecated)
For reference, you can learn more about Statiq here:
After picking an SSG framework, the next decision to make is about content. Where will you store the content that powers your website? Basically, you have two main paths to pick from. Do you add markdown files within your source code that will eventually be converted into HTML pages, or, do you want to get your content from an external source, like a headless CMS?
The first thing to know about Statiq is that it runs in two modes. The default mode will automatically convert any markdown files that it finds within your project and then convert them into HTML pages automatically. This all works magically without you needing to write any additional code yourself.
The good thing about using this mode is that after installing Statiq, all you need to really worry about is the presentation. To create a website in this mode, all you need to add is the styling and presentation bits for your pages. Personally, I think this mode is great for portfolio projects and basic brochureware websites, however, needing to push a new commit every time you want to update the website is not ideal!
Statiq can also be configured to run in a second mode. In this second mode, you can customize how the framework executes. This means you could get your content from a database, a headless CMS, or wherever makes you happy. Adding this type of behavior is done by adding custom pipelines. Creating a pipeline is simple enough, create a class and inherit from
Pipeline. Within this custom pipeline, you can then get content from any data source. It is worth pointing out that there are two existing headless CMS pipelines that have already been created for Statiq that are currently available within Nuget, these are:
You are free to create your own pipelines, however, this could be a reason to pick one of these CMS systems for your next project. The big thing to be aware of in this mode is that you may need to create a lot of pipelines. As a general rule of thumb, you will need to create a new pipeline for each type of page that you want to create. On a large project, you might need to create 20-30 different pipelines. For example, you might need to create a pipeline for the homepage, a pipeline to render the landing pages, a pipeline to render all blog pages, one for layouts and menus, etc...
Statiq Sample Sites on Github
To help you get going with Static, I have created two sample sites that demonstrate how it works that you can clone and test locally. You can get access to both of these sample sites from my Github here:
The reason for the two sites is to demonstrate how each of the modes works. The markdown site uses the default markdown for HTML processing. This site also makes use of a theme. Statiq also has the ability for you to use a theme instead of having to start from scratch. Currently, I can only find a single theme for Statiq which is the Clean Blog Theme. The good thing to note about Statiq themes is that they are easy to extend and change to your needs!
Statiq Setup Guide
To get going with Statiq, create a brand new .NET Core console project. Currently, Statiq only supports .NET 6 so do not use 7 or 8! After doing this you can then install Statiq via Nuget. The commands to do these steps are below:
The next step is to initialize Statiq within
Program.cs. The code you need to add here will depend on the mode you want to use. To use the default markdown to HTML code (which is much easier to use), you would add this code within
To add your own custom pipelines and get data by other means involves using this config:
One important thing to note about these snippets is around previewing the site. Statiq contains a preview server so you can open up your website within a browser and view and test your amends while you work. In the default mode (
CreateWeb()) this is handled for you. When you use the custom mode with
CreateDefault() you will need to enable the preview server yourself. This is why we have the second line
The other thing to note is that the preview server will not launch correctly unless you have a
launchSettings.json within your project. If you do not have one, create a folder in your root called
Properties, create a file called
launchSettings.json and in it add some code like this:
Within a Statiq project that are a few key folders you need to create, these are:
- Input: This is where you add your markdown files, CSS files, layouts, JS files, etc....
- Theme: This is any external themes that you install should live
- Output: This is where the generated HTML files will be created
- Properties: To add your launch settings file!
If you are running Statiq in the default mode if you add a markdown file within
input, run a
dotnet run, and you should see a file that is created with a matching name within
The next thing to consider are themes and templates. At the time of writing there only appears to be a single theme for Statiq. You will install this theme as a Git submodule within your project. One thing to note about the theme you can configure it by adding certain settings within
appsettings.json. You can do that using this command:
Before installing this theme, you need to consider the type of website that you want to build. For simple portfolio or brochureware websites, use the theme to speed up development. I would not advise using this theme if you want to use custom pipelines or if you want to use the Razor templating engine to render your pages within Statiq. The reason for this is that the views within this theme expect an object of type
IDocument to be passed into them.
IDocument is the type that all pages that are rendered in Statiq are built with.
If you are building a more complex project, it is likely that you will want to have more control over what data your pages need to render correctly. If you are restricted to only returning data within an object of type
IDocument you will have limited control over what you can return. For example, if you want to render a listing page that relies on data that is pulled from multiple data sources, assuming you want to use the Razor template engine to render pages, you will be better off creating a custom view model and passing that object from your pipeline into the view directly.
Being able to create custom view models that only expose the data required by the view will make your life easier. If you attempt to do this while using the theme you will encounter an error. The theme expects you to return all data within the pipelines as
IDocument when you return a view model a type mismatch exception will occur. While it is possible to change the template, by the time you waste debugging and fixing stuff you will likely have been better starting from scratch!
With the warning about themes made, let us start to think about custom pipelines. If you are working in custom mode, you can pick from three out-of-the-box ways on how you would like to render your pages. These options are:
- Convert pages from markdown to HTML
- Use handlebars and handlebar syntax to map data to HTML
- Use Razor to render pages
These are the out-of-the-box options that you can use straight away, however, you can also add custom ones as well. Within Statiq these rendering options are known as modules. Modules allow you to alter the data within a pipeline while it is executing. Modules are added within a custom pipeline within event handlers. As part of a pipeline's lifecycle, you can have four main event handlers to hook into. It is worth noting that within Statiq these event handlers are called phases. Statiq supports four phases:
- Input Phase
- Process Phase
- Post-Process Phase
- Output Phase
The code below defines a custom pipeline that I created to render a homepage. The pipeline reads a markdown file in the input phase, it then uses several modules in the processing phase to get the data required to create the page. One output of this phase is to return a view model that is used within the corresponding homepage view. Finally, in the output phase, the page is converted into an actual HTML file within the
In terms of the code, you can see the class inherits from
Pipeline. In the constructor, I hook into three event handlers,
OutputModules. Within the input phase, we get the homepage metadata and content from a markdown file. Even though this example reads from markdown, this could be changed to get data from a CMS instead.
InputModules phase, I use the
ReadFile module. This module will define what pages will be created while the pipeline is executing. Next, within the
ProcessModules phase I use four modules that access and manipulate the data required to render the page. It is within this phase that I define the page view model and pass it down into the view. Let us break down what each model does:
ExtractFrontMatter: This reads the content and metadata and loads it into the current request from the markdown file
MergeContent: This merges in the view that will be used to render the page
RenderRazor: Tells the pipeline to use the Razor templating engine. Here I create the view model and pass it to the view ('Home.cshtml')
SetDestination: Defines the name of the HTML file that will be created
OutputModules we add the code to create the page on disk. The next thing to mention about creating a custom pipeline is that Statiq comes with more out-of-the-box modules than the five I use within this pipeline. The homepage pipeline I define above makes use of the Razor view engine, however, Statiq also has modules that work with handlebars and markdown. Some of the modules you can use include
One negative that is definitely worth flagging here is around lack of documentation. I found it pretty difficult to find good implementation information on how to create pipelines. Rendering a single page with its page content was simple enough, however, I definitely struggle to figure out how to render additional page metadata within my request like the menu.
Trying to figure out the best way to access menu data and data about other pages from within the currently executing pipeline was not a trivial problem to solve. The Statiq documentation is not great at explaining what modules to use and in what circumstances. How you build a pipeline when you use handlebars is different compared to how you build a pipeline that uses the Razor view engine. The same is true if you are accessing your data off of a disk compared to from a database. The documentation is definitely not clear on how you should bunch modules together for these different scenarios and this is the reason why I wasted a bunch of time trying to figure out how to get everything working!
When you want to read data from a markdown file, the pipeline will differ compared to getting data from a CMS. Expect to waste a good many hours tweaking and debugging your pipelines to make it work the way you need it to. I found getting data about the current page easy enough, however, on each page you will also need to get the data for the site furniture. Accessing global data for your layout, menu data, and footer data is definitely not straightforward. I wasted about 2 days trying to figure out how to render the additional bits and bobs on my pages. This was frustrating.
The sample site I created and linked to above is the only example I can see online of how to build a custom pipeline with a menu and a layout using Razor. In production, rendering page content is only half the battle, so again be warned that if you want to use Statiq, when guessing how long a project will take, expect the guess will be on the longer side the first time around! To solve this issue, I ended up creating a specific
MenuPipeline. The job of this pipeline is to get all the menu items that we want to render. Below lists what this pipeline looks like:
The key thing to point out about this code is that the output of the pipeline is to render all the pages that I want to include within my menu in a folder called
menu within the
output. Next, I create a layout pipeline. Within this pipeline, I reference the output of the
Dependencies.Add(nameof(MenuPipeline)) as seen below:
Next, within each pipeline, I inherit from this new
LayoutPipeline rather than the default Statiq pipeline. The benefit of doing this is that within each pipeline within the Statiq page context object, within the
OutputPages property, you will have access to the menu items. With the menu items in the page context, within your view, you can render your menu using a loop like this:
The only thing to note about this code is that the
IsPage property comes from metadata that I add within the current pages' related markdown file.
How To Host A Statiq site in Netlify
Personally, I think the biggest benefit of this approach is that you can now make use of the newer hosting providers. Providers like Netlify, Vercel, Cloudflare pages etc... have a lot of benefits compared to hosting a site in IIS or docker.
Deploying a Statiq site in one of these newer hosting providers is simple. The big wins from these providers include:
- Your site lives on the edge so caching is simple
- Statiq pages are fast
- Static pages cant break due to code issues
- Statiq sites are cheaper to host
- Most providers offer free tiers so hosting is cheap
- Most providers also offer serverless functions, AB testing, storage, etc...
Getting started with Netlify is simple. Netlify supports .NET 6 so all you need to do is within your project create a new file called
netlify.toml. In this file you need to add these commands:
Next, create an account with Netlify, and create a new site. You can do this via Github. See my related video on how to do this as this process literally takes minutes.
The other thing to note for Windows devs is that Netlify builds on a Unix server. This means all file references are case-sensitive. If you try referencing a view with a capital letter or the wrong casing, the build will fail, so be warned! This means that sometimes your project will build locally but fail when you push it... so be warned!
Statiq Review and Considerations
All in all, I was impressed with Statiq, however, obviously no framework is perfect and the big issues I encountered include:
How to implement layouts, headers, and footers: I definitely struggled to figure out how to build the page's additional site furniture. My final solution for this sample site was to create a pipeline that generated all the pages I wanted to render in the menu as HTML pages. I'm not sure if this is the optimal way, however, I could not find any info on better methods to deal with this scenario.
Limited implementation documentation: The Statiq documentation details the what, but it needs to cover the how. This criticism builds upon the point above, it is very tough to figure out how to build pipelines. Statiq provides lots of modules that you can use, however, there is limited information on how you should combine these modules together.
Be wary when using Statiq as you will likely need to figure a lot of stuff out yourself. If you are under time pressures this might not be ideal!
Happy Coding 🤘