Creating Your First Site
In this tutorial, you'll create your first content site using MyLittleContentEngine. By the end, you'll have a working Blazor application that can serve markdown content and generate static HTML files.
What You'll Build
You'll create a simple blog-style website with:
- A home page displaying recent posts
- Individual blog post pages
Prerequisites
Before starting, ensure you have:
- .NET 9 SDK or later installed
- A code editor (Visual Studio, VS Code, or JetBrains Rider)
- Familiarity with command-line tools
- 1
Create a New Blazor Project
Start by creating a new empty ASP.NET project:
dotnet new web -n MyFirstContentSite cd MyFirstContentSite - 2
Add My
Little Content Engine Add the NuGet package references to your project, ensuring you use the
--prereleaseoption:dotnet add package MyLittleContentEngine --prerelease dotnet add package MyLittleContentEngine.MonorailCss --prereleaseMyLittleContentEnginecontains the core functionality for content management, whileMyLittleContentEngine.MonorailCssprovides a simple CSS framework for styling.The
MyLittleContentEngine.MonorailCsspackage is optional, butMyLittleContentEnginemakes a lot of assumptions regarding styling that you'd otherwise have to unravel without it. We'll use it in this example to keep things simple. - 3
Build Your Metadata Model
Create a model to define the structure of your blog post metadata. Add a new file
BlogFrontMatter.cs:public class BlogFrontMatter : IFrontMatter { public string Title { get; init; } = "Empty title"; public string Description { get; init; } = string.Empty; public string? Uid { get; init; } = null; public DateTime Date { get; init; } = DateTime.Now; public bool IsDraft { get; init; } = false; public string[] Tags { get; init; } = []; public string? RedirectUrl { get; init; } public string? Section { get; init; } public Metadata AsMetadata() { return new Metadata() { Title = Title, Description = Description, LastMod = Date, RssItem = true }; } } - 4
Configure the Content Engine
Open
Program.csand configure MyLittleContentEngine. Replace the existing content with:using MinimalExample; using MinimalExample.Components; using MyLittleContentEngine; using MyLittleContentEngine.MonorailCss; var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorComponents(); // configures site wide settings // hot reload note - these will not be reflected until the application restarts builder.Services.AddContentEngineService(_ => new ContentEngineOptions { SiteTitle = "My Little Content Engine", SiteDescription = "An Inflexible Content Engine for .NET", ContentRootPath = "Content", }).WithMarkdownContentService(_ => new MarkdownContentOptions<BlogFrontMatter>() { ContentPath = "Content", BasePageUrl = string.Empty }); builder.Services.AddMonorailCss(); var app = builder.Build(); app.UseAntiforgery(); app.MapStaticAssets(); app.MapRazorComponents<App>(); app.UseMonorailCss(); await app.RunOrBuildContent(args); - 5
Create the Content Structure
Create the content directory structure to match what we defined in
WithMarkdownContentService:mkdir -p Content - 6
Write Your First Blog Post
Create your first blog post at
Content/index.md. Make sure to include the front matter at the top of the file that matches theBlogFrontMattermodel you created earlier. Here's an example:--- title: "Welcome to My Content Site" description: "Getting started with MyLittleContentEngine" date: 2025-01-15 tags: - introduction - welcome isDraft: false --- Welcome to my new content site! This is my first post using MyLittleContentEngine. ## What is MyLittleContentEngine? MyLittleContentEngine is a static site generator built specifically for .NET Blazor applications. It allows you to: - Write content in Markdown - Use `dotnet watch` to develop your site - Generate static HTML for fast loading at deployment ## Getting Started Creating content is as simple as writing Markdown files with YAML front matter. The engine handles the rest! Except the writing! ```csharp var theAnswer = 30 + 25; ``` And that's it! You now have a basic content site up and running with MyLittleContentEngine. - 7
Create Your Layout
Create
Components/Layout/MainLayout.razorto include basic styling. This uses Tailwind CSS like syntax for styling. Here we are defining a simple layout for our blog posts, centered in the middle of the page:@inherits LayoutComponentBase <div> <div class="max-w-4xl mx-auto p-4"> <div class="flex flex-col"> <main class="flex-1 w-full"> @Body </main> </div> </div> </div> @code{ protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); } } - 8
Create the Home Page
Create
Components/Layout/Home.razorto display your blog posts. Here we are callingGetAllContentPagesAsyncto retrieve all the blog posts and display them in a list:@page "/" @using System.Collections.Immutable @inject ContentEngineOptions ContentEngineOptions @inject IMarkdownContentService<BlogFrontMatter> MarkdownContentService @if (_pages == null) { <p>Loading...</p> return; } <h1 class="text-4xl font-bold pb-4">Welcome @ContentEngineOptions.SiteTitle!</h1> <ul class="list-disc pl-6 space-y-2"> @foreach (var p in _pages.Where(p => p.Url.Trim('/') != "index").OrderByDescending(i => i.FrontMatter.Date)) { <li><a href="@p.Url">@p.FrontMatter.Title</a></li> } </ul> @code { private ImmutableList<MarkdownContentPage<BlogFrontMatter>>? _pages; protected override async Task OnInitializedAsync() { _pages = await MarkdownContentService.GetAllContentPagesAsync(); await base.OnInitializedAsync(); } } - 9
Create a Page Displaying Component
Create
Components/Layout/Pages.razorto display individual blog posts. Here we are callingGetRenderedContentPageByUrlOrDefaultto retrieve the blog post by its URL:@page "/{*fileName:nonfile}" @inject ContentEngineOptions ContentEngineOptions @inject IMarkdownContentService<BlogFrontMatter> MarkdownContentService @if (_postContent == null || _post == null) { <PageTitle>@ContentEngineOptions.SiteTitle</PageTitle> <p>Not found</p> return; } <PageTitle>@ContentEngineOptions.SiteTitle - @_post.FrontMatter.Title</PageTitle> <article> <header> <h1 class="text-4xl font-bold"> @_post.FrontMatter.Title</h1> </header> <div class="prose max-w-full"> @((MarkupString)_postContent) </div> </article> @code { private MarkdownContentPage<BlogFrontMatter>? _post; private string? _postContent; [Parameter] public required string FileName { get; init; } = string.Empty; protected override async Task OnInitializedAsync() { var fileName = FileName; if (string.IsNullOrWhiteSpace(fileName)) { fileName = "index"; } var page = await MarkdownContentService.GetRenderedContentPageByUrlOrDefault(fileName); if (page == null) { return; } _post = page.Value.Page; _postContent = page.Value.HtmlContent; } } - 10
Configure
dotnet watchSupportThe last step we need to do is to ensure that the content files are watched for changes during development. Add the following to your
.csprojfile:<ItemGroup> <Watch Include="Content\**\*.*" /> </ItemGroup> - 11
Test Your Site
Run your site in development mode:
dotnet watchNavigate to
https://localhost:5001(or the URL shown in your terminal) to see your site in action!
While the page is open, try editing the Content/index.md file. You should see the changes reflected
immediately without needing to restart the server. Not just editing, but adding, renaming and deleting files
should also work seamlessly.
What Success Looks Like
When dotnet watch is running, navigate to the URL shown in your terminal (typically http://localhost:5131).
You'll see:
- A home page listing your blog post(s) with titles and links
- Clicking a post title takes you to the full post rendered from your Markdown file
- The page uses basic styling from MonorailCSS
Try editing Content/index.md and saving — the browser refreshes automatically within a second or two. Try
adding a second .md file in the Content/ directory and watch it appear in the post list without a restart.
Next Steps
- Connecting to Roslyn — embed live, verified code examples from your solution
- Using UI Elements — add sidebar navigation and page outlines
- Deploying to GitHub Pages — publish your site automatically