ASP.NET Core on IIS: the WeAmp.PageSpeed shape
Updated 2026-05-20 · We-Amp B.V. · about an 8-minute read
The most common .NET enterprise topology we see is ASP.NET Core deployed in-process behind IIS, fronted by the ASP.NET Core Module (ANCM). Customers ask us a recurring question: where does image and CSS optimization fit when the request flows through IIS, then ANCM, then Kestrel, then back out? Architectural map first, then the practical Program.cs.
The request path, drawn out
ANCM is a native IIS module that forwards incoming requests into an
in-process Kestrel instance hosted inside the IIS worker process
(w3wp.exe). The path looks like:
browser
-> IIS HTTP.SYS
-> w3wp.exe (IIS worker)
-> ASP.NET Core Module (in-process)
-> Kestrel
-> ASP.NET Core middleware pipeline
-> your application
Two things follow from that path. First, the IIS-level handlers
(static-file handler, dynamic compression, URL rewrite) only see
requests that hit IIS-managed paths, not requests that ANCM forwards
to Kestrel. Second, ASP.NET Core's
UseStaticFiles() middleware serves the static assets,
which means any request-time optimization has to happen inside the
ASP.NET Core pipeline, not at the IIS layer.
That is why the IISpeed-style native IIS module is the wrong layer for an ASP.NET Core application. The mod_pagespeed 1.1 IIS module plugs into the IIS request pipeline at a level that ANCM has already bypassed for ASP.NET Core requests. The module is the right tool for classic ASP.NET, WebForms, and SharePoint, but the wrong tool for an application whose every request gets handed off to Kestrel.
Where WeAmp.PageSpeed.AspNetCore fits
WeAmp.PageSpeed.AspNetCore is the ASP.NET Core middleware
sibling of mod_pagespeed 1.1. Same PageSpeed Automatic optimization
library underneath, repackaged for the
IApplicationBuilder idiom and shipped as a NuGet
package. The middleware sits inside the Kestrel pipeline, so it sees
every request whether you host behind IIS via ANCM, run Kestrel
directly behind a reverse proxy, or run a container on a different
OS entirely.
Registration in Program.cs
using WeAmp.PageSpeed.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddPageSpeed(options =>
{
options.RewriteLevel = "CoreFilters";
options.EnableFilters = new[]
{
"recompress_images",
"convert_jpeg_to_webp",
"resize_images",
"insert_image_dimensions",
"lazyload_images",
"prioritize_critical_css",
"defer_javascript",
"combine_javascript",
"extend_cache",
};
options.FileCachePath = @"C:\ProgramData\WeAmp.PageSpeed\cache";
options.FileCacheSizeKb = 1_048_576;
});
builder.Services.AddControllers();
builder.Services.AddRazorPages();
var app = builder.Build();
// Order matters: UsePageSpeed before UseStaticFiles so the middleware
// can intercept static-asset responses; before MapControllers so it
// can rewrite HTML responses from the application.
app.UsePageSpeed();
app.UseStaticFiles();
app.MapControllers();
app.MapRazorPages();
app.Run();
The middleware reads each response, decides whether to optimize
(HTML, CSS, JavaScript, images), and rewrites the body in place.
Bytes that should pass through (
application/json API responses, file downloads,
anything the application explicitly marks no-rewrite) skip the
optimization layer.
Filters worth knowing about
The middleware exposes the same filter set as mod_pagespeed 1.1. Five do most of the Core Web Vitals work:
-
recompress_images,convert_jpeg_to_webp, andresize_images: the image trio. The middleware generates a WebP variant of each JPEG and serves it viaAcceptnegotiation, recompresses oversized PNGs, and resizes images to the dimensions the HTML actually declares. Hero-image weight typically drops by an order of magnitude on image-heavy Razor pages. -
prioritize_critical_css: extracts the above-the-fold CSS rules and inlines them in the document head, deferring the rest. First paint stops blocking on a stylesheet round trip. -
defer_javascriptandcombine_javascript: move non-critical JavaScript out of the critical path and reduce the number of round trips to fetch script bundles. Interaction to Next Paint improves on heavy Razor pages with multiple jQuery plugins or vendored widgets.
Interaction with IIS-level features
Hosting ASP.NET Core behind IIS means a handful of IIS-level features are still in the path even though they do not see the application bytes:
- IIS Static Content Compression. Leave it on. The
middleware emits CSS and JavaScript that compresses well, and the
IIS compression layer applies gzip / Brotli to the response before
it leaves
w3wp.exe. The two features stack rather than overlap. - HTTP/2. IIS terminates HTTP/2 at HTTP.SYS and
downgrades the in-process forward to HTTP/1.1, so Kestrel receives
an HTTP/1.1 request from ANCM regardless of what the client spoke.
The middleware's behaviour is the same on HTTP/1.1 and HTTP/2; no
change in configuration. Server-push hints are emitted via
Link: rel=preloadheaders, which IIS forwards untouched. - URL Rewrite Module. Any URL rewrite rules in
web.configexecute at the IIS layer before ANCM hands off to Kestrel; they affect what URL the application sees but do not interact with the optimization layer. - Application-pool identity and the cache directory.
The middleware writes optimized variants to the configured
FileCachePath. The path must be writable by the application-pool identity (commonlyIIS APPPOOL\<site>). The default location underC:\ProgramData\WeAmp.PageSpeed\cacheis created with the right ACLs by the NuGet package's first-run setup.
When to skip the middleware
Three cases where adding the middleware does not buy you anything:
- Blazor Server. Blazor Server renders the bulk of the page over a persistent SignalR connection, not as HTTP responses the middleware can rewrite. The image and CSS optimization still applies to the initial document and any static assets, but Interaction to Next Paint on a Blazor Server application is dominated by SignalR round-trip latency, which the middleware cannot touch. Architectural improvements (Blazor WebAssembly, Blazor Auto, or moving expensive interactions client-side) are the right tool there.
- Pure JSON APIs. If the application only emits
application/jsonfrom controller endpoints, there is nothing for the middleware to rewrite. The optimizations apply to HTML, CSS, JavaScript, and image responses. Run it for the static-asset side only if the API has a companion documentation site. - You already run a CDN with image-optimization features turned on. Two image optimizers in sequence usually overlap without benefit. Pick one layer (either the CDN or the middleware) and run the other in pass-through.
Pure-Kestrel deployment
The same registration works unchanged when you take IIS out of the
picture entirely: Kestrel directly on a Linux container, fronted by
nginx as a reverse proxy, in Docker on a developer workstation.
That portability is part of the reason ASP.NET Core teams pick
middleware over the IIS-module path: the same optimization runs in
every environment the application runs in, including the local
dotnet run loop.
See the
ASP.NET
Core middleware deep-dive on modpagespeed.com for the longer
write-up: performance characteristics, the P/Invoke layer that
bridges the C# pipeline to the native PageSpeed Automatic library,
and the configuration shape under
appsettings.json.
Where to start
The
WeAmp.PageSpeed.AspNetCore
NuGet package is Preview today. Install it and run unlicensed to
evaluate: it fully optimizes and just adds an
X-PageSpeed-Warn: unlicensed header. When you are ready
for production,
buy a license — an
immediate-charge subscription via FastSpring, and the same license
covers the middleware on every platform you deploy to. A commercial
license is required for production use. Existing IISpeed customers
moving to ASP.NET Core can apply the
IISpeed
license transfer at no cost against the middleware as well as the
1.1 IIS module.
Add WeAmp.PageSpeed to an ASP.NET Core app
Add two lines to Program.cs and run unlicensed to
evaluate — it optimizes regardless, adding an
X-PageSpeed-Warn: unlicensed header until you buy.
A commercial license is required for production use.
Related
- IISpeed transition overview — what changed, how the license transfers, and where the optimization library lives now.
- IIS image optimization with WebP and AVIF — the classic-IIS path alongside the ASP.NET Core path.
- IIS Core Web Vitals in 2026 — measurement workflow and the filters that move each metric.
- ASP.NET Core middleware deep-dive — how the NuGet package wraps the PageSpeed Automatic library.
- ASP.NET Core image optimization in C# — a worked example with measurements.
- IISpeed successor: mod_pagespeed 1.1 for IIS — the canonical successor page.