I love many things about static site generators, but I mainly enjoy the challenge of building a fast user experience, given development time constraints. The constraints force me to be mindful of every new feature I add, the content I want to present, and the resources I use to accomplish all my goals. I believe that most, if not all, experiences on the web could benefit from some element of static site generation. While that may or may not be true, I believe in the inverse statement. Some static sites could benefit from a dynamic backend to provide unique and personalized experiences.

This post will explore adding 11ty, one of my favorite static site generator tools, to an ASP.NET Core application. We’ll see how this approach will allow us to build out static content while taking advantage of a dynamic and programmable hosting platform. Let’s get started.

Setting up an ASP.NET Core Web Project for 11ty

Let’s start with an ASP.NET Core Empty template, a barebones solution. We’ll be adding a few files and folders. First, let’s create a new site folder. This directory will hold all our static content that 11ty will process into static files.

The next step is creating a package.json file to manage our NPM packages. Finally, we’ll create a build script to process our static content. Here are the contents of /site/package.json.

{
  "name": "site",
  "private": true,
  "scripts": {
    "build": "node node_modules/.bin/eleventy"
  },
  "devDependencies": {
    "@11ty/eleventy": "^2.0.0-beta.3"
  }
}

From here, we can run npm install to install all the development dependencies. Feel free to add any other NPM packages for your static site. 11ty has a lot of great options too, so I imagine many folks will.

The next step is to add an eleventy.config.js file, which will help the 11ty process copy assets to our destination of wwwroot.

module.exports = function (eleventyConfig) {
    // copy images, css, and js from /assets
    eleventyConfig.addPassthroughCopy("./assets")
    return {
        dir: {
            passthroughFileCopy: true,
            // write to aspnet.core output directory
            output: "../wwwroot"
        }
    }
}

If you’re checking this project into source control, as you should, you may want to ignore the contents of wwwroot as they are artifacts of the build process and will likely be changing regularly.

Next, let’s add some content and templates for 11ty to process. First, create a new folder under site called _includes. Once you’ve made the folder, add the following layout.html file.


<!DOCTYPE html>
<html lang="en">
<head>
    <title>{{ title }}</title>
    <meta name="Description" content="{{ description }}" />
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <link rel="stylesheet" type="text/css" href="/assets/styles.css" />
</head>
<body>
<div class="content">{{ content }}</div>
<script src="/assets/script.js"></script>
</body>
</html>

Next, create an assets folder under site and add two files, script.js and styles.css. We’ve referenced these files in the layout, but we need some content. Here is the content for our JavaScript file. The stylesheet content can be empty for now.

;(function () {
    console.log("Hello there!")

    fetch('/api/current-time')
        .then((response) => response.json())
        .then((data) => {
            const el =document.getElementById('target');
            el.innerText =`the current date time is ${data.datetime}`; 
        });
})()

Spoiler, we’ll be implementing an ASP.NET Core API endpoint and calling it from our static files.

Finally, let’s add an index.md file. 11ty allows you to write your content in multiple templating languages, with my favorite being plain-old markdown.


---
title: "Hello 11ty"
layout: layout.html
description: Hosting 11ty on ASP.NET Core
---

# This Is Eleventy Served from ASP.NET Core

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

<h1 id="target">Loading...</h1>

Let’s get back to the world of .NET. The following section will set up our build process and the ASP.NET Core pipeline.

Setting Up ASP.NET Core to build 11ty

First, we need MSBuild to execute our NPM build script. That’s as straightforward as adding a Target to the .csrpoj file.

<Project Sdk="Microsoft.NET.Sdk.Web">

    <PropertyGroup>
        <TargetFramework>net7.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
    </PropertyGroup>

    <ItemGroup>
      <Folder Include="wwwroot" />
    </ItemGroup>

    <Target Name="11ty" BeforeTargets="Build">
        <Exec Command="npm install &amp;&amp; npm run build" WorkingDirectory="site" />
    </Target>

</Project>

It’s important to set the WorkingDirectory attribute to site, or else NPM will not find the script.

The next phase in our process is to configure our ASP.NET Core pipeline. We only need two pieces of middleware: DefaultFilesMiddleware and StaticFilesMiddleware. We’ll also take this opportunity to add our ASP.NET Core Minimal API endpoint.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseDefaultFiles();
app.UseStaticFiles();

app.MapGet("/api/current-time", () => new
{
    datetime = DateTime.Now
});

app.Run();

And that’s it! You now have a working 11ty and ASP.NET Core web project. I’ve uploaded it to my GitHub repository if you’d like a quicker starting point.**

As always, thanks for reading, and I hope this helps you. If you enjoyed this post, please share it with friends and colleagues.