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 8 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 Blazor Server project:
dotnet new blazorserver-empty -n MyFirstContentSite cd MyFirstContentSite
- 2
Add MyLittleContentEngine
Add the NuGet package references to your project:
dotnet add package MyLittleContentEngine dotnet add package MyLittleContentEngine.MonorailCss
MyLittleContentEngine
contains the core functionality for content management, whileMyLittleContentEngine.MonorailCss
provides a simple CSS framework for styling.The
MyLittleContentEngine.MonorailCss
package is optional, butMyLittleContentEngine
makes 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
Add Static Content Service
Create a model to define the structure of your blog post metadata. Add a new file
BlogPost.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 Metadata AsMetadata() { return new Metadata() { Title = Title, Description = Description, LastMod = Date, RssItem = true }; } }
- 4
Configure the Content Engine
Open
Program.cs
and configure MyLittleContentEngine. We'll break down what each components does bit by bit in the guides, but for now 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", BaseUrl = Environment.GetEnvironmentVariable("BaseHref") ?? "/", ContentRootPath = "Content", }); // configures individual sections of the blog. PageUrl should match the configured razor pages route, // and contentPath should match the location on disk. // you can have multiple of these per site. builder.Services.AddContentEngineStaticContentService(_ => new ContentEngineContentOptions<BlogFrontMatter>() { ContentPath = "Content", BasePageUrl = string.Empty }); builder.Services.AddMonorailCss(); var app = builder.Build(); app.UseAntiforgery(); app.UseStaticFiles(); 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
AddContentEngineStaticContentService
:mkdir -p Content
- 6
Write Your First Blog Post
Create your first blog post at
Content/index.md
:--- 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.razor
to 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. It uses a flexbox layout which we can later use to extend the design with a sidebar or other components. For now, it will just center the content.@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/Home.razor
to display your blog posts. Here we are callingGetAllContentPagesAsync
to retrieve all the blog posts and display them in a list.@page "/" @using System.Collections.Immutable @using MyLittleContentEngine @using MyLittleContentEngine.Models @using MyLittleContentEngine.Services.Content @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.OrderByDescending(i => i.FrontMatter.Date)) { <li><a href="@p.NavigateUrl">@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 Page
Create
Components/Page.razor
to display your blog posts. Here we are callingGetRenderedContentPageByUrlOrDefault
to retrieve the blog post by its URL.@page "/{*fileName:nonfile}" @using MyLittleContentEngine.Models @using MyLittleContentEngine.Services.Content @using MyLittleContentEngine @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; } }
- 6
Configure
dotnet watch
SupportThe last step we need to do is to ensure that the content files are watched for changes during development. Add the following to your
.csproj
file:<ItemGroup> <Watch Include="Content\**\*.*"/> </ItemGroup>
- 11
Test Your Site
Run your site in development mode:
dotnet watch
Navigate 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.