![]() |
VOOZH | about |
dotnet add package AspNetStatic --version 0.27.8
NuGet\Install-Package AspNetStatic -Version 0.27.8
<PackageReference Include="AspNetStatic" Version="0.27.8" />
<PackageVersion Include="AspNetStatic" Version="0.27.8" />Directory.Packages.props
<PackageReference Include="AspNetStatic" />Project file
paket add AspNetStatic --version 0.27.8
#r "nuget: AspNetStatic, 0.27.8"
#:package AspNetStatic@0.27.8
#addin nuget:?package=AspNetStatic&version=0.27.8Install as a Cake Addin
#tool nuget:?package=AspNetStatic&version=0.27.8Install as a Cake Tool
👁 Platform Support: ASP.NET Core 6.0+
👁 License: Apache 2
👁 Build-And-Test
Okay, so you want to create a static website. After doing some research, you learn that all the cool kids are using tools like Jekyll, Hugo, Gatsby, or Statiq. But what you also learn is that all of these tools require you to learn an entirely new way of constructing sites and pages. And this is when you'll wonder to yourself, as I did, Why!? I already know how to use ASP.NET Core to create websites; why do I need to learn & use a whole other stack just for SSG? Isn't there a better way that lets me use the tools and skills I already have?
Well, now there is!
AspNetStatic lets you generate a static website with the same ASP.NET Core tools you love and use every day. Just add this package and tell it which routes (page, css, js, etc.) to process.
AspNetStatic works equally well with Blazor (SSR), Razor Pages, and MVC (controllers + views).
Blazor pages must not rely on any client-side (JS, WASM) functionality for rendering, or any behaviors like showing a placeholder (e.g. a spinner) before rendering the actual content. The rule-of-thumb (for any tech you want to use with AspNetStatic) is that as long as the content has completed rendering by the time AspNetStatic receives it (via http request), then it will work fine.
AspNetStatic can also be used in a mixed mode configuration where some of the pages in your site are static html files (generated with the same _layout & page layers that define the look & feel of the rest of your site), while others remain dynamically generated per request. See Partial Static Site under Scenarios section below.
Build your ASP.NET site the way you always have. AspNetStatic doesn't have any opinions about how you should structure your server-rendered site. AspNetStatic is not a framework. It's not a CMS. There's no blog engine. It has no templating system. AspNetStatic does just one thing; create static files for selected routes in your ASP.NET Core project. That means you can use whatever framework, component, package, or architectural style you like. Want to use a blog engine? No problem. Want to use a CMS? No problem. Want to create a documentation site using a markdown processor to render page content? No problem! AspNetStatic doesn't care; it will create optimized static files no matter how the content is produced by the server.
<br/>
It's a piece of cake.
dotnet add package AspNetStatic
StaticResourcesInfoProvider (or an object that derives from StaticResourcesInfoProviderBase or implements the IStaticResourcesInfoProvider interface)PageResources and/or OtherResources collections
Route property of each itemIStaticResourcesInfoProvider attributes as appropriatebuilder.Services.AddSingleton<IStaticResourcesInfoProvider>(
new StaticResourcesInfoProvider()
.AddAllProjectRazorPages(builder.Environment) // from AspNetStaticContrib project
.AddAllWebRootContent(builder.Environment)); // from AspNetStaticContrib project
-- OR --
builder.Services.AddSingleton<IStaticResourcesInfoProvider>(
new StaticResourcesInfoProvider(
new []
{
new PageResource("/"),
new PageResource("/privacy"),
new PageResource("/blog/articles/posts/1") { OutFile = "blog/post-1.html" },
new PageResource("/blog/articles/posts/2") { OutFile = "blog/post-2-dark.html", Query = "?theme=dark" },
new CssResource("/bootstrap/bootstrap.min.css") { OptimizationType = OptimizationType.None },
new CssResource("/site.css"),
new JsResource("/site.js"),
new BinResource("/favicon.png")
}));
...
app.MapRazorPages();
...
app.GenerateStaticContent(@"C:\SSG-Output-Folder");
app.Run();
dotnet run
You can use AspNetStatic in traditional SSG mode (generate files and exit the app), or in a 'partial-static site' mode. There is also an option to periodically regenerate the static content while your app is running. See the Scenarios section below for details.
<br/>
Keep the following in mind when specifying routes in the IStaticResourcesInfoProvider.PageResources collection.
PageResources collection (see OutFile property). The only requirement is that the specified path be relative to the destination root folder. If you do not specify a value for OutFile, the pathname for the generated file will be determined as demonstrated below.PageResources collection. The route parameters are treated as part of the route, and are used in constructing the output file pathname.PageResources collection (see Query property). You can specify the same Route with different Query values, but you will need to specify a unique OutFile value for each instance of that route.PageResources collection (see OptimizationType property). The default optimization type setting, OptimizationType.Auto, automatically applies the appropriate optimization.PageResources collection (see OutputEncoding property). Default is UTF8.1: Content optimization options apply only when content optimization is enabled. Please see the Content Optimization section below for details.
All of the above also applies to routes for CSS, JavaScript, and binary (e.g. image) files specified in the OtherResources collection property.
| Url<br/>(route + query) | Always Default<br/>false | Always Default<br/>true |
|---|---|---|
| / | C:\MySite\index.html | C:\MySite\index.html |
| /index | C:\MySite\index.html | C:\MySite\index.html |
| /index/ | C:\MySite\index\index.html | C:\MySite\index\index.html |
| /page | C:\MySite\page.html | C:\MySite\page\index.html |
| /page/ | C:\MySite\page\index.html | C:\MySite\page\index.html |
| /page/123 | C:\MySite\page\123.html | C:\MySite\page\123\index.html |
| /page/123/ | C:\MySite\page\123\index.html | C:\MySite\page\123\index.html |
| /page/123?p1=v1 | C:\MySite\page\123.html | C:\MySite\page\123\index.html |
| /page/123/?p1=v1 | C:\MySite\page\123\index.html | C:\MySite\page\123\index.html |
| /blog/articles/ | C:\MySite\blog\articles/index.html | C:\MySite\blog\articles\index.html |
| /blog/articles/post1 | C:\MySite\blog\articles\post1.html | C:\MySite\blog\articles\post1\index.html |
| Url<br/>(route + query) | Generated File |
|---|---|
| /file.css | C:\MySite\file.css |
| /folder/file.css | C:\MySite\folder\file.css |
| /file.css?v=123 | C:\MySite\file.css |
| /file | C:\MySite\file.css (CssResource) |
| /file/ | C:\MySite\file.css (CssResource) |
| /file.js | C:\MySite\file.js |
| /folder/file.js | C:\MySite\folder\file.js |
| /file.js?v=123 | C:\MySite\file.js |
| /file | C:\MySite\file.js (JsResource) |
| /file/ | C:\MySite\file.js (JsResource) |
| /file.png | C:\MySite\file.png |
| /folder/file.png | C:\MySite\folder\file.png |
| /file.png?v=123 | C:\MySite\file.png |
| /file | C:\MySite\file.bin (BinResource) |
| /file/ | C:\MySite\file.bin (BinResource) |
| Url<br/>(route + query) | Is Static Route: false<br/><br/> | Is Static Route: true<br/>Always Default: false | Is Static Route: true<br/>Always Default: true |
|---|---|---|---|
| / | /index.cshtml | /index.html | /index.html |
| /index | /index.cshtml | /index.html | /index.html |
| /index/ | /index/index.cshtml | /index/index.html | /index/index.html |
| /page | /page.cshtml | /page.html | /page/index.html |
| /page/ | /page/index.cshtml | /page/index.html | /page/index.html |
| /page/123 | /page.cshtml | /page/123.html | /page/123/index.html |
| /page/123/ | /page.cshtml | /page/123/index.html | /page/123/index.html |
| /page/123?p1=v1 | /page.cshtml | /page/123.html | /page/123/index.html |
| /page/123/?p1=v1 | /page.cshtml | /page/123/index.html | /page/123/index.html |
| /blog/articles/ | /blog/articles/index.cshtml | /blog/articles/index.html | /blog/articles/index.html |
| /blog/articles/post1 | /blog/articles/post1.cshtml | /blog/articles/post1.html | /blog/articles/post1/index..html |
The same rules apply when links in static files are updated to refer to other generated static pages.
In ASP.NET Core, UrlHelper (and the asp-* tag helpers) generate link URIs based on the routing configuration of your app, so if you're using them, be sure to specify an appropriate value for alwaysDefaultFile, as shown below. (NOTE: Specify the same value if/when configuring the fallback middleware).
// Sample routes: /, /index, and /page
//-------------------------------------
// generated links: /, /index, and /page
builder.Services.AddRouting(
options =>
{ // default configuration in ASP.NET Core
options.AppendTrailingSlash = false;
});
...
// fallback static pages: /index.html, /index.html, and /page.html
builder.Services.AddStaticPageFallback(
cfg =>
{
cfg.AlwaysDefaultFile = false;
});
...
// generated static pages: /index.html, /index.html, and /page.html
app.GenerateStaticContent(
alwaysDefaultFile: false);
-- OR --
// generated links: /, /index/, and /page/
builder.Services.AddRouting(
options =>
{
options.AppendTrailingSlash = true;
});
...
// fallback static pages: /index.html, /index/index.html, and /page/index.html
builder.Services.AddStaticPageFallback(
cfg =>
{
cfg.AlwaysDefaultFile = true;
});
...
// generated static pages: /index.html, /index/index.html, and /page/index.html
app.GenerateStaticContent(
alwaysDefaultFile: true);
<br/>
In all scenarios, please ensure that routes for static content are unincumbered by authentication or authorization requirements or anything else that requires user interaction.
In this scenario, you want to generate a completely static website (to host on Netlify or Azure/AWS storage, for instance). Once the static pages are generated, you will take the files in the destination folder and xcopy deploy them to your web host.
Sample Configuration 1:
href attribute for <a> and <area> tags that refer to static pages (e.g. /page to /page.html).
app.GenerateStaticContent(
"../SSG_Output",
exitWhenDone: true); // exit the app after static generation
Sample Configuration 2:
href attribute for <a> and <area> tags that refer to static pages.// true when app is executed with one of the marker args, such as SSG.
// dotnet run -- ssg
var exitWhenDone = args.HasExitWhenDoneArg();
app.GenerateStaticContent(
@"C:\path\to\destination\root\folder",
exitWhenDone: exitWhenDone,
alwaysDefaultFile: true,
dontUpdateLinks: true);
If you want to omit static-file generation while you're still developing the site, you can configure a launchSettings profile for SSG mode operation. To enable this, you would surround the GenerateStaticContent() call with an IF gate.
"profiles": {
"SSG": {
"commandName": "Project",
"commandLineArgs": "ssg",
"launchBrowser": false,
"applicationUrl": "https://localhost:5000",
}
}
Then, in the startup code (Program.cs)
if (args.HasExitWhenDoneArg())
{
app.GenerateStaticContent(
@"path\to\destination\root\folder",
exitWhenDone: true,
);
}
Now you can use the SSG profile to launch your app in SSG mode (to generate static content, then exit), and a differrent launch profile while you're in development mode, editing the site content. (The BlazorSSG sample demonstrates this approach.)
In this scenario, you want some of the pages in your ASP.NET Core app to be static, but still want other routes to be served as dynamic content per request (e.g. pages/views, JSON API's, etc.). When the app runs, static (.html) files will be generated for routes you specify. The website will then serve these static files for the specified routes, and dynamic content (as usual) for others.
While static files are being generated, requests to routes for which a static file has not yet been generated will be served as dynamicly generated content (using the source .cshtml page). Once the static file for that route has been generated, it will be used to satisfy subsequent requests.
The configuration options are generally the same as for a standalone static site, except the following differences:
app.Environment.WebRoot (i.e. wwwroot).href of <a> and <area> tags).Like this:
...
builder.Services.AddStaticPageFallback();
...
app.UseStaticPageFallback(); // re-route to the static file (page resources only)
app.UseStaticFiles();
...
app.UseRouting();
...
app.Map...();
...
app.GenerateStaticContent(
app.Environment.WebRoot, // must specify wwwroot
exitWhenDone: false, // don't exit after generating static files
alwaysDefaultFile: true/false,
dontUpdateLinks: false); // update links so they refer to static files
...
app.Run();
The fallback middleware only re-routes requests for routes that match entries in the PageResources collection, and does so only if a generated static file exists for that route.
If the data used in the content of static files changes while the app is running, you can configure periodic regeneration by specifying a value for the regenerationInterval parameter in the GenerateStaticContent() call. This will result in static files being generated when the app starts, and then periodically based on the specified interval.
app.GenerateStaticContent(
...
exitWhenDone: false,
regenerationInterval: TimeSpan.FromHours(2) // re-generate static files every 2 hours
);
<br/>
Before proceeding, let's clarify what an "optimizer" is in AspNetStatic. An optimizer is simply a component that performs some sort of processing on the content retrieved for a static resource (i.e. page, css, js, image, etc.) An optimizer is called by AspNetStatic after the content of a resource is retrieved, but just before that content is written to the destination file.
The "optimizer" feature in AspNetStatic is enabled by default and requires no configuration to use.
To disable the feature, pass true as the argument for the dontOptimizeContent parameter in the GenerateStaticContent call.
app.GenerateStaticContent(
...
dontOptimizeContent: true);
This will prevent the IOptimizerSelector and any optimizers from being called.
The optimizer to be executed by AspNetStatic for a given resource (page, css, etc.) is determined by the registered IOptimizerSelector component, which by default is DefaultOptimizerSelector.
An IOptimizerSelector implementation can select an optimizer based on the attributes of the resource (e.g. the resource type, its stated OptimizationType, the source or destination file extension or path).
DefaultOptimizerSelector uses the resource type and OptimizationType information to select an optimizer.
If a given resource requests no optimization (OptimizationType.None), or an optimizer implementation is not available for that resource type, DefaultOptimizerSelector will return one of the built-in "null" optimizers (NullMarkupOptimizer, NullCssOptimizer, NullJsOptimizer, and NullBinOptimizer).
To use your own custom selector, implement the IOptimizerSelector interface and register it in the DI container.
public class MyCustomOptimizerSelector : IOptimizerSelector
{
public IMarkupOptimizer SelectFor(PageResource pageResource, string outFilePathname) { ... }
public ICssOptimizer SelectFor(CssResource cssResource, string outFilePathname) { ... }
public IJsOptimizer SelectFor(JsResource jsResource, string outFilePathname) { ... }
public IBinOptimizer SelectFor(BinResource binResource, string outFilePathname) { ... }
}
...
builder.Services.AddSingleton<IOptimizerSelector, MyCustomOptimizerSelector>();
builder.Services.AddDefaultOptimizers(); // register default optimizers & minifiers
builder.Services.AddSingleton<IJsMinifier, YuiJsMinifier>(); // override default minifier
builder.Services.AddSingleton<IMarkupOPtimizer, MyCustomMarkupOptimizer>()); // override default optimizer
If your custom IOptimizerSelector implementation injects one or more of the default optimizers provided by AspNetStatic, you must register them by calling AddDefaultOptimizers().
AspNetStatic supports the following optimizer types:
IMarkupOptimizer: Called when processing PageResource objectsICssOptimizer: Called when processing CssResource objectsIJsOptimizer: Called when processing JsResource objectsIBinOptimizer: Called when processing BinResource objectsAspNetStatic provides "default" implementations for these interfaces: DefaultMarkupOptimizer, DefaultCssOptimizer, and DefaultJsOptimizer. There is no default IBinOptimizer implementation.
To use a custom optimizer, implement (and register in DI) the relevant interface. You can derive and extend the "default" implementation, if you wish. For instance, if you want to perform some pre and post processing operations on CSS resources (in addition to the built-in minification), derive and extend the DefaultCssOptimizer, like so:
// OPTION 1: Implement from scratch
public MyCustomCssOptimizer : ICssOptimizer
{
public CssOptimizerResult Execute(string content, ...)
{
// your custom processing here...
}
}
// OPTION 2: inherit from DefaultCssOptimizer
public MyCustomCssOptimizer : DefaultCssOptimizer
{
public override CssOptimizerResult Execute(string content, ...)
{
content = DoPreProcessing(content);
var result = base.Execute(content, ...); // do usual minification
result.OptimizedContent = DoPostProcessing(result.OptimizedContent);
return result;
}
private string DoPreProcessing(string css) { ... }
private string DoPostProcessing(string css) { ... }
}
// -- OPTION 3: create decorator over DefaultCssOptimizer
public MyCustomCssOptimizer(
DefaultCssOptimizer defaultCssOptimizer) :
ICssOptimizer
{
public CssOptimizerResult Execute(string content, ...)
{
content = DoPreProcessing(content);
var result = defaultCssOptimizer.Execute(content, ...); // do usual minification
result.OptimizedContent = DoPostProcessing(result.OptimizedContent);
return result;
}
private string DoPreProcessing(string css) { ... }
private string DoPostProcessing(string css) { ... }
}
...
// register default optimizers & minifiers
builder.Services.AddDefaultOptimizers();
// Register your custom implementation...
builder.Services.AddSingleton<ICssOptimizer, MyCustomCssOptimizer>();
// Call following method if you want DefaultCssOptimizer to use
// the AspNetStatic defaults for these services, otherwise it will
// use the WebMarkupMin internal defaults.
// This call is not needed if you've already called AddDefaultOptimizers.
builder.Services.AddDefaultMinifiers();
The default optimizers provided by AspNetStatic (DefaultMarkupOptimizer, DefaultCssOptimizer, and DefaultJsOptimizer) use the WebMarkupMin package to minify HTML, CSS and JS content.
To override the default minification settings used by AspNetStatic, register the appropriate objects as described below.
For details about WebMarkupMin configuration settings, please consult the WebMarkupMin documentation.
AspNetStatic uses the default WebMarkupMin configuration settings (determined internally by WebMarkupMin) for minifying HTML, XHTML, and XML content. To override this behavior, register one or more of the following configuration objects:
using WebMarkupMin.Core;
// HTML minifier settings
builder.Services.AddSingleton(
sp => new HtmlMinificationSettings()
{
...
});
// XHTML minifier settings
builder.Services.AddSingleton(
sp => new XhtmlMinificationSettings()
{
...
});
// XML minifier settings
builder.Services.AddSingleton(
sp => new XmlMinificationSettings()
{
...
});
AspNetStatic uses KristensenCssMinifier for ICssMinifier, and CrockfordJsMinifier for IJsMinifier by default. To override this behavior, register alternative implementations:
using WebMarkupMin.Core;
// ICssMinifier
builder.Services.AddSingleton<ICssMinifier>(
sp => new YuiCssMinifier(...));
// IJsMinifier
builder.Services.AddSingleton<IJsMinifier>(
sp => new YuiJsMinifier(...));
<br/>
<br/>
If you like this project, or find it useful, please give it a star. Thank you.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 is compatible. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
Showing the top 2 NuGet packages that depend on AspNetStatic:
| Package | Downloads |
|---|---|
|
AspNetStaticContrib
AspNetStatic community contributed extensions. |
|
|
AspNetStaticContrib.Stekeblad
AspNetStatic community contributed extensions. |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.27.8 | 253 | 4/29/2026 |
| 0.27.7 | 588 | 3/13/2026 |
| 0.27.6 | 105 | 3/12/2026 |
| 0.27.5 | 97 | 3/10/2026 |
| 0.27.4 | 93 | 3/6/2026 |
| 0.27.3 | 74 | 3/6/2026 |
| 0.27.2 | 682 | 12/31/2025 |
| 0.27.1 | 250 | 12/22/2025 |
| 0.27.0 | 533 | 12/11/2025 |
| 0.26.7 | 442 | 12/10/2025 |
| 0.26.6 | 1,213 | 6/5/2025 |
| 0.26.5 | 246 | 5/20/2025 |
| 0.26.4 | 698 | 1/5/2025 |
| 0.26.3 | 219 | 1/5/2025 |
| 0.26.2 | 205 | 1/5/2025 |
| 0.26.1 | 211 | 1/5/2025 |
| 0.26.0 | 229 | 1/4/2025 |
| 0.25.0 | 222 | 1/4/2025 |
| 0.24.0 | 972 | 8/4/2024 |
| 0.23.0 | 184 | 8/3/2024 |