WeAmp.PageSpeed.AspNetCore
2.0.15
Prefix Reserved
dotnet add package WeAmp.PageSpeed.AspNetCore --version 2.0.15
NuGet\Install-Package WeAmp.PageSpeed.AspNetCore -Version 2.0.15
<PackageReference Include="WeAmp.PageSpeed.AspNetCore" Version="2.0.15" />
<PackageVersion Include="WeAmp.PageSpeed.AspNetCore" Version="2.0.15" />
<PackageReference Include="WeAmp.PageSpeed.AspNetCore" />
paket add WeAmp.PageSpeed.AspNetCore --version 2.0.15
#r "nuget: WeAmp.PageSpeed.AspNetCore, 2.0.15"
#:package WeAmp.PageSpeed.AspNetCore@2.0.15
#addin nuget:?package=WeAmp.PageSpeed.AspNetCore&version=2.0.15
#tool nuget:?package=WeAmp.PageSpeed.AspNetCore&version=2.0.15
WeAmp.PageSpeed
Drop-in ASP.NET Core middleware that improves Core Web Vitals without touching your app code. Adds critical CSS inlining, LCP preload injection, lazy loading, and on-demand WebP/AVIF image transcoding to every HTML response — powered by the C++23 PageSpeed engine via P/Invoke, with zero-copy cache hits.
Single-package install. Hot-reloadable config. 14-day trial via the in-app
console at /console/. Paid plans at modpagespeed.com/pricing/.
Quick Start
1. Install the package
dotnet add package WeAmp.PageSpeed.AspNetCore
The matching native binaries for your runtime (linux-x64, linux-arm64,
osx-arm64, or win-x64) come in transitively -- no separate NativeAssets
package reference required.
2. Register the middleware in Program.cs
using WeAmp.PageSpeed.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddPageSpeed();
var app = builder.Build();
app.UsePageSpeed(); // before anything that writes the response body
app.UseStaticFiles();
app.Run();
That is the whole setup. With no Worker configuration, the worker process
starts automatically and coordinates over an auto-resolved per-process socket,
so HTML and image optimization are on out of the box. You only add a Worker
section when you want to change that — see Configuration.
3. Start a trial
Run your app and navigate to http://<your-app-host>/console/ in a browser.
Enter your email to start a 14-day trial. The token is persisted to the cache
volume and reused on subsequent runs.
Without a license token, requests pass through unchanged. When the trial
expires, purchase a subscription and
paste the issued key into the same /console/ page.
4. How do I know it's working?
Three checks, from quickest to most thorough.
Hit a content route and look for the X-PageSpeed header:
curl -i http://localhost:5050/
HTTP/1.1 200 OK
X-PageSpeed: HIT
HIT means the response was served from the optimized cache; MISS means the
worker is building the variant and you'll see HIT on the next request. (The
/console/* routes are short-circuited before the middleware, so they
intentionally do not carry X-PageSpeed — only content routes like / and
your assets do.)
Open http://localhost:5050/console/dashboard — once a few requests have run,
the Dashboard and Metrics show non-zero counts. If everything reads zero, see
How do I know it's working?
in the docs.
Confirm image transcoding via content negotiation. We don't rewrite URLs in
2.0 — the same /hero.jpg URL serves WebP to WebP-capable clients and AVIF to
AVIF-capable ones, selected by the request Accept header:
curl -s -o /dev/null -D - http://localhost:5050/hero.jpg -H 'Accept: image/jpeg'
# Content-Length: 98230 Content-Type: image/jpeg Vary: Accept, Save-Data, User-Agent
curl -s -o /dev/null -D - http://localhost:5050/hero.jpg -H 'Accept: image/webp'
# Content-Length: 2422 Content-Type: image/webp Vary: Accept, Save-Data, User-Agent
curl -s -o /dev/null -D - http://localhost:5050/hero.jpg -H 'Accept: image/avif'
# Content-Length: 415 Content-Type: image/avif Vary: Accept, Save-Data, User-Agent
Same URL, materially smaller bytes, and a Vary: Accept, Save-Data, User-Agent
header so caches keep the variants apart. (Byte counts are from one sample
image; yours will differ.)
What It Does
- HTML optimization -- critical CSS inlining, lazy loading, LCP preload injection, third-party preconnect hints
- Image transcoding -- on-demand conversion to WebP and AVIF, viewport-aware resizing, Save-Data support
- CSS/JS minification -- whitespace removal, comment stripping
- Intelligent caching -- zero-copy cache hits via memory-mapped Cyclone cache volumes, up to 194 cache variants per resource
The middleware buffers HTML responses, passes them through the native
libpagespeed library, and notifies the worker process to generate optimized
asset variants asynchronously.
Platform Support
| RID | Status | Notes |
|---|---|---|
| linux-x64 | Supported | glibc 2.34+ (RHEL 9 / Ubuntu 22.04 / Debian 12 or newer) |
| linux-arm64 | Supported | glibc 2.34+ |
| osx-arm64 | Supported | macOS 13+ (Apple Silicon) |
| win-x64 | Supported | Windows 10/11, Server 2019+ |
Native binaries (libpagespeed, factory_worker) are bundled with the
matching WeAmp.PageSpeed.NativeAssets.* package, pulled in transitively by
WeAmp.PageSpeed.AspNetCore. The worker process starts automatically on
application boot. The Linux binaries statically link the C++ runtime
(libc++/libc++abi/libunwind), so no additional shared libraries need to be
present on the host beyond the system glibc.
Each NativeAssets package also ships a BUILD_INFO.json file at
runtimes/<rid>/native/BUILD_INFO.json with git_sha, git_sha_short,
build_timestamp_utc, and rid. It is intended for support correlation --
matching compliance-report heartbeats (which emit the worker's git_commit)
to a specific package build -- and for verifying package provenance without
running the worker.
Configuration
Add a PageSpeed section to appsettings.json:
{
"PageSpeed": {
"Cache": {
"VolumePath": "/var/cache/pagespeed/volume.dat",
"VolumeSizeBytes": 1073741824
}
}
}
Key options:
| Setting | Default | Description |
|---|---|---|
Cache.VolumePath |
/var/cache/pagespeed/volume.dat |
Path to the Cyclone cache volume file |
Cache.VolumeSizeBytes |
1073741824 (1 GB) |
Maximum cache volume size |
Worker.AutoStart |
true |
Launch and manage the worker process on startup. Set false to run no worker. |
Worker.SocketPath |
unset | Worker-coordination socket. Leave it unset (the default) for an auto-resolved per-process socket with coordination on. Set to a concrete path to share one socket with an out-of-process worker. Setting it to null or "" disables coordination and logs a startup warning. |
Worker.ApiPort |
0 (auto, loopback only) |
Override to expose the worker HTTP API on a fixed port |
Console.MountPath |
/console |
URL prefix for the in-app workbench |
Enabled |
true |
Enable/disable the middleware (supports hot-reload) |
LicenseKey |
null |
License key (base64url-encoded Ed25519 token) |
Options support hot-reload via IOptionsMonitor<PageSpeedOptions>.
Worker IPC
The middleware ↔ worker channel uses Unix domain sockets on Linux and macOS,
and Windows Named Pipes on win-x64. Selection is automatic; no configuration
required. The console and license endpoints are served on the app port; the
worker's HTTP API is bound to 127.0.0.1 on an ephemeral port by default and
is not exposed externally unless you set Worker.ApiPort explicitly.
Security
The /console/ workbench and /v1/license/* proxy are served on the
same origin as your app. The worker's CSRF gate (Content-Type: application/json + X-Requested-With: XMLHttpRequest) blocks
cross-origin form posts, but does not protect against same-origin
scripts. In particular, a third-party script loaded into your app (XSS
or supply-chain JavaScript) can drive POST /v1/license/trial —
burning your one-trial-per-email quota or triggering a trial-issuance
email to an attacker-controlled address.
Treat /console/ as an admin surface:
- Set
Console.RequireHttps = truein production. - Don't expose
/console/to untrusted networks — gate at your reverse proxy or useConsole.MountPathto move the path off a guessable location. - Avoid loading untrusted third-party scripts into apps with this middleware enabled.
A one-time CSRF token for trial activation is planned for a follow-up release.
License
Licensed under the Business Source License 1.1 (BUSL-1.1).
- Change Date: Four years from each release
- Change License: Apache License 2.0
After the Change Date, each version becomes available under Apache 2.0. See the LICENSE file in the package for full terms.
Links
- modpagespeed.com/pricing/ -- plans, pricing, and purchase
- modpagespeed.com/features/#aspnet-core -- ASP.NET Core overview
- modpagespeed.com -- product documentation and filter reference
- we-amp.com -- We-Amp B.V.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.IO.RecyclableMemoryStream (>= 3.0.1)
- WeAmp.PageSpeed (>= 2.0.15)
-
net8.0
- Microsoft.IO.RecyclableMemoryStream (>= 3.0.1)
- WeAmp.PageSpeed (>= 2.0.15)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.0.15 | 37 | 5/23/2026 |
| 2.0.14 | 37 | 5/23/2026 |
| 2.0.13 | 44 | 5/23/2026 |
| 2.0.12 | 44 | 5/22/2026 |
| 2.0.11 | 44 | 5/22/2026 |
| 2.0.10 | 40 | 5/22/2026 |
| 2.0.9 | 46 | 5/22/2026 |
| 2.0.8 | 46 | 5/22/2026 |
| 2.0.7 | 77 | 5/19/2026 |
| 2.0.6 | 91 | 5/18/2026 |
| 2.0.5 | 90 | 5/18/2026 |
| 2.0.4 | 90 | 5/18/2026 |
| 2.0.3 | 86 | 5/17/2026 |
| 2.0.2 | 89 | 5/17/2026 |
| 2.0.1 | 103 | 5/17/2026 |
| 2.0.0 | 84 | 5/17/2026 |
2.0.14: trial-experience fix. Worker.SocketPath now defaults to an auto-resolved per-process socket when Worker.AutoStart is true (its default), so AddPageSpeed() + UsePageSpeed() optimizes out of the box — previously the default was null, which silently disabled worker coordination and left notifications.received, variants.written, and every Dashboard/Savings/Metrics counter at zero. Set SocketPath to null/empty to deliberately disable coordination (now logs a startup warning); set a concrete path for an out-of-process worker. Reference samples no longer ship the "demo mode" SocketPath=null. Docs/README/FAQ clarify that 2.0 does not rewrite URLs — optimized image variants are served at the original URL via content negotiation (Vary: Accept). No breaking API change.
2.0.13: the embedded /console/ now reaches all of its data pages in the in-process middleware sample — Dashboard, Configuration, URLs, Savings, and Metrics proxy /v1/cache, /v1/stats, /v1/capture, and /v1/config through to the worker (previously only License and Health were wired, so those pages 404'd outside the Docker demo). The worker now reports its real version on /v1/health, generated from VERSION.txt at build time (it had been pinned at 2.0.7 since that release). Release-pipeline reliability fixes; no public API change.
2.0.7: NuGet page + build-metadata polish — README now points readers at https://modpagespeed.com/pricing/ (three visible CTAs in lead, trial step, and Links). CI builds set Deterministic + ContinuousIntegrationBuild, so nuget.info's deterministic + compiler-flags health checks go green and shipped PDBs no longer embed local source paths. No middleware/worker code change vs 2.0.6.
2.0.6: republish 2.0.5 with worker version string corrected — kPageSpeedVersion was stale at 2.0.4 in 2.0.5, so /v1/health and heartbeat telemetry mis-reported the install version on every install. Pinned via a new VersionInvariantTests case that checks version.h, Directory.Build.props, and VERSION.txt agree on every PR.
2.0.5: hotfix — trial + paid activation now work on a stock install. The middleware was not wiring the upstream license-service URL into the worker; new WorkerOptions.LicenseRenewalUrl (default https://api.modpagespeed.com/) closes that gap. License-service allow-list updated in lockstep to accept the NuGet middleware's server=aspnetcore identifier (was silently 400'd by the prod license service since #377).
2.0.4: workbench /console/ now served by the middleware out of the box — trial activation no longer requires Worker.ApiPort. NOTICE + THIRD-PARTY-NOTICES are now packed.
2.0.3: hotfix linux-x64 GLIBC_2.38 baseline; 2.0.2 unlisted.
2.0.2: Linux native binaries statically link libc++/libc++abi/libunwind — dotnet add + dotnet run works end-to-end.
2.0.1: single-package install (NativeAssets pulled transitively).
2.0.0: General Availability. Microsoft Trusted Signing + nuget.org Trusted Publishing.