WeAmp.PageSpeed
2.0.32
Prefix Reserved
dotnet add package WeAmp.PageSpeed --version 2.0.32
NuGet\Install-Package WeAmp.PageSpeed -Version 2.0.32
<PackageReference Include="WeAmp.PageSpeed" Version="2.0.32" />
<PackageVersion Include="WeAmp.PageSpeed" Version="2.0.32" />
<PackageReference Include="WeAmp.PageSpeed" />
paket add WeAmp.PageSpeed --version 2.0.32
#r "nuget: WeAmp.PageSpeed, 2.0.32"
#:package WeAmp.PageSpeed@2.0.32
#addin nuget:?package=WeAmp.PageSpeed&version=2.0.32
#tool nuget:?package=WeAmp.PageSpeed&version=2.0.32
WeAmp.PageSpeed for ASP.NET Core
Drop-in ASP.NET Core middleware that improves Core Web Vitals without touching your app code. It adds critical CSS inlining, LCP preload injection, lazy loading, and on-demand WebP/AVIF image transcoding to every HTML response. The C++23 PageSpeed engine runs in-process via P/Invoke and serves cache hits zero-copy.
Single-package install. Hot-reloadable config. Optimization runs out of the
box, so you can evaluate it without a license. While unlicensed, responses
carry an X-PageSpeed-Warn: unlicensed header. Production use requires a
commercial license — but the software never locks you out.
Buy or apply a key from the in-app console at /console/ — see 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();
No extra wiring required: with no Worker section, the worker process starts
automatically and coordinates over an auto-resolved per-process socket. Add a
Worker section only to change that — see Configuration.
Optimization is on by default; see step 3 to license it for production.
3. License it for production
You can evaluate the middleware right away. Production use requires a
commercial license — but the software never locks you out: until a license is
applied, every response carries an X-PageSpeed-Warn: unlicensed header and
the console shows an unlicensed notice. To license it, run your app and
navigate to http://<your-app-host>/console/ in a browser, then
buy a subscription — monthly or annual,
billed immediately, cancel anytime. The issued key is applied automatically and
persisted to the cache volume, then reused on subsequent runs. You can also
paste an existing key into the same /console/ page. (/console/ is an admin
surface — see Security before exposing it beyond localhost.)
4. How do I know it's working?
Three checks, from quickest to most thorough. These examples assume your app
listens on :5050 (set ASPNETCORE_URLS=http://localhost:5050 or adjust the
URLs to your port) and serves at least one HTML page and one image.
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/. 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
- Zero-copy caching: cache hits served from the memory-mapped Cyclone cache with no copy, up to 36 optimized variants per resource (format × viewport × density × Save-Data)
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.
On Linux and want a reverse proxy instead of in-process middleware?
WeAmp.PageSpeed.Sidecar
runs mod_pagespeed 1.15 as a bundled nginx + ngx_pagespeed reverse proxy in front of
Kestrel (Linux-only). Use it when you want the classic nginx module in a sidecar; use
this package for cross-platform in-process optimization with WebP/AVIF.
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. Only Cache is shown below;
every key in the table is optional and falls back to its default.
{
"PageSpeed": {
"Cache": {
"VolumePath": "/var/cache/pagespeed/volume.dat",
"VolumeSizeBytes": 1073741824
}
}
}
Set Cache.VolumePath to a location that is writable in your environment. The
default /var/cache/pagespeed/ assumes a Linux host; on Windows, macOS, or
containers without that path, point it somewhere writable (for example
./cache/volume.dat or %TEMP%).
Top-level keys:
| Setting | Default | Description |
|---|---|---|
Enabled |
true |
Enable/disable the middleware (supports hot-reload) |
LicenseKey |
null |
License key (base64url-encoded Ed25519 token) |
ExcludePaths |
["/api/", "/signalr/", "/_blazor/", "/_framework/"] |
URL prefixes the middleware leaves untouched (supports hot-reload) |
CacheMode |
Safe |
Safe: must-revalidate on assets. Aggressive: public + stale-if-error on assets. HTML is always no-cache. |
MaxResponseBufferBytes |
5242880 (5 MB) |
Responses larger than this pass through unmodified |
CssMaxAgeSeconds |
300 |
max-age on CSS/JS cache HIT responses |
ImageMaxAgeSeconds |
1800 |
max-age on image cache HIT responses |
Cache section:
| Setting | Default | Description |
|---|---|---|
Cache.VolumePath |
/var/cache/pagespeed/volume.dat |
Path to the Cyclone cache volume file (must be writable) |
Cache.VolumeSizeBytes |
1073741824 (1 GB) |
Maximum cache volume size |
Worker section:
| Setting | Default | Description |
|---|---|---|
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 section:
| Setting | Default | Description |
|---|---|---|
Console.MountPath |
/console |
URL prefix for the in-app console |
Console.RequireHttps |
false |
Reject non-HTTPS requests to the console (set true in production) |
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/ admin console and /v1/license/* proxy are served on the same
origin as your app. The worker requires Content-Type: application/json and
X-Requested-With: XMLHttpRequest on POSTs to /v1/license/*, which blocks
cross-origin form posts. It does not protect against same-origin scripts: a
third-party script loaded into your app (via XSS or a supply-chain dependency)
can drive a license POST such as POST /v1/license/apply to install an
attacker-supplied key, because that endpoint is auth-exempt to allow
bootstrapping before an API token is configured.
Treat /console/ as an admin surface:
- Set
Console.RequireHttps = truein production. - Don't expose
/console/to untrusted networks. Gate it 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.
License
Licensed under the Business Source License 1.1 (BUSL-1.1).
- Change Date: Four years after the first public release of each version (see the Change Date in the packaged LICENSE file).
- 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
- WeAmp.PageSpeed.Sidecar — mod_pagespeed 1.15 as a bundled-nginx sidecar (Linux)
- 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
- WeAmp.PageSpeed.NativeAssets.Linux (>= 2.0.32)
- WeAmp.PageSpeed.NativeAssets.macOS (>= 2.0.32)
- WeAmp.PageSpeed.NativeAssets.Windows (>= 2.0.32)
-
net8.0
- WeAmp.PageSpeed.NativeAssets.Linux (>= 2.0.32)
- WeAmp.PageSpeed.NativeAssets.macOS (>= 2.0.32)
- WeAmp.PageSpeed.NativeAssets.Windows (>= 2.0.32)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on WeAmp.PageSpeed:
| Package | Downloads |
|---|---|
|
WeAmp.PageSpeed.AspNetCore
Drop-in ASP.NET Core middleware that improves Core Web Vitals without touching your app code: critical CSS inlining, LCP preload, lazy loading, and on-demand WebP/AVIF transcoding on every HTML response. The C++23 PageSpeed engine runs in-process via P/Invoke. Single-package install with transitive native binaries for linux-x64, linux-arm64, osx-arm64, and win-x64. Optimization runs unlicensed for evaluation (X-PageSpeed-Warn header). Production use requires a commercial license; the software never locks you out. |
GitHub repositories
This package is not used by any popular GitHub repositories.
2.0.32: Security hardening for the shipped native library. The optimizer `.so` is now built with full RELRO, immediate binding (`BIND_NOW`), stack-smashing protection, and `FORTIFY_SOURCE`, and a release-time assertion fails the build if any of these protections are missing (MPS2-SEC-05). The bundled image codecs (libavif, libaom, libyuv) are updated to pick up upstream CVE fixes. There are no API, wire, or configuration changes.
2.0.30: The image optimizer no longer strips Content Credentials (C2PA) during optimization (ADR-081). Recompression previously dropped the APP11/JUMBF manifest. Now JPEG-to-JPEG recompression carries it through, and the other paths (AVIF, WebP, viewport resize, PNG, XMP) serve the original bytes unchanged when a manifest is present rather than stripping it. This manifest-preserving behavior is on by default. An additional opt-in flag, `--c2pa-carry`, re-splices a PNG's provenance chunks after recompression, so a manifest-bearing PNG can stay both optimized and signed rather than served in its original form. Several worker correctness fixes. The FastSpring storefront validator rejected every value because of an off-by-one in its suffix comparison, leaving `--fastspring-storefront` and the matching config field inert; the comparison now uses an explicit `.onfastspring.com` suffix match. Critical-CSS extraction now keeps rules scoped by `:where()` and `:is()` (and rules where such a wrapper follows another pseudo-class), which previously dropped Tailwind v4 dark-mode utilities and produced a light-mode flash on dark sites. HEAD requests to `/console/` and `/v1/` endpoints now follow the same read-open authorization as GET when `PAGESPEED_API_READ_OPEN=true`, so HEAD-based monitoring probes no longer get a false 401. The "JS minification had parse errors" warning is now logged once per resource instead of on every cache refresh of a permanently unparseable file. Two concurrency-safety fixes in the worker make license-renewal shutdown deterministic and the lag timer race-free. SVG serve counts are now recorded at serve time through the shared serve-stats store, so `pagespeed_svg_served_total` and `/v1/stats` svg.served report real numbers. In the supporting libraries, the width-to-viewport C-ABI used by .NET P/Invoke now classifies the 1024–1279px band as Tablet, matching the engine's own viewport range at the 1280 split. The markdown extractor used for agent feeds now escapes a leading block marker on a list item's first line, closing a path that could forge markdown structure (headings, quotes, tables) into the output. ASP.NET Core middleware: when the default cache path is not writable, the middleware now raises an actionable error naming the path and the setting to change, instead of an opaque permission failure, and the getting-started docs show a writable cache path for local `dotnet run`. The .NET `RevalidationRequired` flag no longer aliases the worker-processed bit, so aggressive cache mode again emits `stale-if-error` (rather than `must-revalidate`) on optimized assets. The sample apps now target net8.0 and net10.0. The nginx front-end no longer re-notifies the worker on cache hits of worker-vectorized SVG variants, removing redundant IPC on every such hit, and the Docker nginx entrypoint self-heals cache-directory ownership on start, so a UID change across image rebuilds no longer breaks the `/analyze` PSI cache. This release also includes internal code-quality refactors with no behavior change and dependency bumps clearing flagged CVEs.
2.0.29: The PageSpeed console's URL list (`/console/urls`) and the `/v1/cache/urls` management API no longer time out. The listing handler computed each row's alternate count by walking that URL's Cyclone chain on the worker's single event-loop thread — roughly 0.3–0.7 s of cold-cache I/O per URL, so a 50-row page took 22–44 s and, because the management API and health checks share that thread, blocked `/v1/health` for the duration. The count is now kept in the in-memory URL registry, refreshed on the worker pool thread whenever a notification (or the asynchronous agent-markdown write) changes a URL's alternates, so the listing is a pure in-memory read with no per-row cache traversal. The JSON response is unchanged. Release builds now stamp the commit they were built from. The `gen_build_commit` step could be served a cached build-commit header from the shared build cache, producing a binary that reported a stale commit in `--version` and `/v1/health`; it is no longer cached, so the embedded commit always matches the source.
2.0.28: Async-CSS gains a cold-cache fail-safe (ADR-096) that fixes a flash-of-unstyled-content regression on low-traffic sites: when an external stylesheet is not yet cached the gate keeps it render-blocking and inlines critical CSS instead of async-deferring an unmeasured sheet, re-enabling async automatically once the sheet caches; the in-process ASP.NET Core path gained the same gate. Critical CSS is no longer double-shipped on multi-variant pages (the render-blocking external link is removed by a parser-driven rewriter and the extractor de-duplicates), and the critical-CSS element-tree cutoff no longer flaps. The agent_optimize markdown variant survives an unchanged-origin cache refresh and improves for citation (entity decoding in code spans, UTF-8-safe metadata, fenced-code languages). The Helm chart collapses the worker and nginx image tags into one lockstep tag defaulting to appVersion. No breaking public API change versus 2.0.27.
2.0.27: Cache-churn, console-accuracy, critical-CSS budget, markdown-variant and JS-minifier correctness fixes diagnosed from the worker admin console. The full-page cache content-hash dedup sentinel no longer self-purges a key by re-hashing the worker's own read of the identity slot (it guards on the worker-processed flag and stamps a durable raw-origin hash in the existing v7 metadata field, no wire/ABI change), so the homepage and the agent_optimize markdown variant stop churning. The agent_optimize markdown variant is now served (the origin-refresh rebuild no longer drops the agent request), and its quality improves for citation: entities decoded before structural escaping (non-ASCII preserved, raw fallback + UTF-8 validation), YAML front-matter with title/metadata, and page-level chrome stripped while in-article headers are kept. Critical-CSS inlining is budgeted by a single three-band coverage policy (ADR-096): below 10% coverage the sheet stays render-blocking and the critical subset is inlined; 10-60% inlines and async-defers; at or above 60% both are skipped, stopping the near-whole-sheet double-ship on Tailwind-like profiles. New /v1/stats counters (critical_css_skipped_high_coverage, async_css_suppressed_low_coverage) plus the previously-dead policy.* counters. The async-CSS loader is served at a content-hashed immutable path (/pagespeed_static/async_css.<hash>.js) by both front-ends (ADR-094 follow-up). The JS minifier tokenizer learned arrow-block bodies, optional catch bindings and optional chaining, so modern bundles minify instead of falling back. No breaking public API change versus 2.0.26.
2.0.26: CSP-safe async-CSS loading and honest console bandwidth reporting. Render-blocking stylesheets are deferred (switched to media=print and restored once loaded) by a same-origin external loader served at /pagespeed_static/async_css.js by both the nginx module and the in-process .NET middleware; it uses no inline onload handler, so it works under a script-src 'self' Content-Security-Policy, and a noscript fallback preserves integrity/crossorigin for clients without JavaScript. The inlined critical CSS now improves first paint instead of adding duplicate bytes for no benefit. The worker admin console clamps the dashboard bandwidth-saved headline to zero and discloses when an optimization trades payload for faster paint, rather than showing a negative figure. Additive C ABI: new ps_async_css_loader_path()/ps_async_css_loader_js() getters and an enable_async_css field appended to ps_html_config_t with existing offsets preserved; the native library, nginx module and .NET middleware move in lockstep. On by default in the worker when critical CSS is present; opt-in via EnableAsyncCss in the middleware. See ADR-094. No breaking public API change versus 2.0.25.
2.0.25: Follow-up correctness fix for the agent_optimize markdown variant. The async browser-analysis pass re-read the page HTML from cache; for larger pages the cache slot could be overwritten or purged before the pass ran ("HTML not in cache"), so the markdown variant failed to render and the page fell back to HTML. The worker now passes the raw origin HTML it already holds into the analysis queue (cache re-read kept as fallback), aligning the render source with the content hash the variant is bound to. Companion to 2.0.24. No breaking public API change versus 2.0.24.
2.0.24: Correctness fix for browser-analysis-driven optimization. A degraded browser-analysis pass (headless-Chrome first-contentful-paint CSS coverage over-reporting near-total criticality) could overwrite a good cached optimization profile, inlining the entire stylesheet instead of a critical subset and suppressing the agent_optimize markdown variant for the affected page. The worker now keeps the prior good profile when a pass looks degraded (near-total coverage) while still rendering markdown, and re-analyzes degraded first-seen profiles. No breaking public API change versus 2.0.23.
2.0.23: Correctness release. The full-page cache no longer serves entries past TTL expiry (expired entries revalidate; bounded stale-if-error fallback via the new pagespeed_stale_if_error_max_age directive), and purges are fenced against in-flight optimization writes so a purge can no longer be silently undone. Optimizer fixes: no JavaScript variants from unparseable sources, @import layer()/supports() preserved instead of flattened, SRI-pinned (integrity=) subresources left untouched, and modern-phone DPR detection with correct image Vary. No breaking public API change versus 2.0.22.
2.0.22: Per-site licensing support (ADR-092). License tokens gain optional scope and domain fields (v5), carried and displayed across the worker, middleware and workbench; scope never affects token verification — the software never locks you out. Telemetry reports by scope. The workbench purchase flow points at the per-site Business plan; plans and pricing at https://modpagespeed.com/pricing/. Shipped container images are rebuilt with full CVE remediation (0 Critical / 0 High across worker, nginx and combined). No breaking public API change versus 2.0.21.
2.0.21: Package polish. The README is rewritten for accuracy and clarity — the per-resource optimized-variant ceiling is corrected to 36 (format x viewport x density x Save-Data), the security example points at the real auth-exempt /v1/license/apply endpoint instead of the removed trial endpoint, and the ASP.NET Core options are regrouped into Top-level/Cache/Worker/Console tables. The package Title and Description are tightened (the retired 14-day-trial line is removed). Build hardening: EnableSourceControlManagerQueries=false + IncludeRepositoryUrlInNuspec=false so the private source repository's commit SHA cannot be re-injected into the package's repository metadata. No middleware or worker code change versus 2.0.20.
2.0.19: Documentation alignment with the always-functional licensing model — the README now states that optimization runs out of the box, unlicensed responses carry X-PageSpeed-Warn: unlicensed, and production use requires a commercial license (the software never locks you out). The 14-day trial is retired; subscriptions are billed immediately. Docs-only; no middleware or worker code change versus 2.0.18.
2.0.18: Product enhancements and changes around license handling. See the release notes at https://modpagespeed.com/.
2.0.17: Various bug fixes and enhancements. No public API change versus 2.0.16.
2.0.16: README clarity — the on-NuGet docs now state up front that the middleware is a no-op until you activate a trial or license (requests pass through unchanged), removing the apparent contradiction with the "optimization on out of the box" wording. Docs-only; no middleware or worker code change versus 2.0.15.
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.