![]() |
VOOZH | about |
dotnet add package CosmoApiServer.Core --version 3.8.1
NuGet\Install-Package CosmoApiServer.Core -Version 3.8.1
<PackageReference Include="CosmoApiServer.Core" Version="3.8.1" />
<PackageVersion Include="CosmoApiServer.Core" Version="3.8.1" />Directory.Packages.props
<PackageReference Include="CosmoApiServer.Core" />Project file
paket add CosmoApiServer.Core --version 3.8.1
#r "nuget: CosmoApiServer.Core, 3.8.1"
#:package CosmoApiServer.Core@3.8.1
#addin nuget:?package=CosmoApiServer.Core&version=3.8.1Install as a Cake Addin
#tool nuget:?package=CosmoApiServer.Core&version=3.8.1Install as a Cake Tool
A .NET HTTP server built on System.IO.Pipelines and System.Net.Sockets with support for HTTP/1.1, HTTP/2, and HTTP/3 over QUIC. Includes a middleware pipeline, routing, frontend hosting, and real-time primitives.
| Protocol | Support |
|---|---|
| HTTP/1.1 | Keep-alive, pipelining |
| HTTP/2 | h2c cleartext + ALPN over TLS |
| HTTP/3 | QUIC via System.Net.Quic (libmsquic) on Linux, macOS, and Windows. Request/response trailers, streamed bodies, NDJSON streaming, QPACK, graceful GOAWAY. |
| TLS | SslStream with ALPN (h2 / http/1.1) |
var builder = CosmoWebApplicationBuilder.Create()
.ListenOn(9092)
.UseHttps("cert.pfx", "password")
.UseHttp3();
// Minimal API server
var builder = CosmoWebApplicationBuilder.Create().ListenOn(9092);
var app = builder.Build();
app.MapGet("/health", ctx => {
ctx.Response.WriteJson(new { status = "ok" });
return ValueTask.CompletedTask;
});
app.Run();
All framework integrations share the same three-layer structure:
| Layer | Purpose | Method |
|---|---|---|
| Dev server | Spawn and manage the frontend process | UseViteDevServer / UseAngularDevServer / UseNextDevServer |
| Dev proxy | Forward framework module paths to the dev server | UseViteDevProxy / UseReactDevProxy / UseNextDevProxy / UseAngularDevProxy |
| Production | Serve the built output with compression + SPA fallback | UseStaticFrontend (or framework-specific wrapper) |
UseViteDevServer spawns the frontend dev process as a managed IHostedService, blocking server startup until it is ready. UseViteDevProxy forwards Vite module-graph paths to the dev server so the browser can use a single origin.
builder.UseViteDevServer(o =>
{
o.WorkingDirectory = "frontend";
o.Command = "npm";
o.Arguments = "run dev";
o.ReadyPattern = "Local:";
o.ReadyTimeout = TimeSpan.FromSeconds(45);
o.LogPrefix = "[nuxt]";
});
builder.UseViteDevProxy(o =>
{
o.DevServerUrl = "http://127.0.0.1:3000";
o.ProxiedPrefixes = ["/@vite", "/@fs", "/@id", "/_nuxt", "/__nuxt", "/__vite_ping"];
});
The ViteDevServerService shuts down the child process tree (Kill(entireProcessTree: true)) when the host stops, freeing the dev port cleanly.
Nuxt SSR with server-side API calls (nuxt-auth-utils, useFetch in SSR context)
When Nuxt uses SSR and makes API calls during its startup warmup (e.g. session checks via nuxt-auth-utils), set ReadyPattern = null. This lets the .NET HTTP listener open immediately so the backend is reachable when Nuxt's warmup fires. Without this, the default blocking behaviour delays the listener until after Nuxt is ready — causing ECONNREFUSED during warmup.
builder.UseViteDevServer(o =>
{
o.WorkingDirectory = "frontend";
o.Command = "npm";
o.Arguments = $"exec nuxt dev -- --host 127.0.0.1 --port 3000";
o.ReadyPattern = null; // .NET opens immediately; Nuxt starts in background
o.LogPrefix = "[nuxt]";
o.Environment["NUXT_SESSION_PASSWORD"] = "...";
o.Environment["API_BASE"] = "http://127.0.0.1:9183";
});
// For SSR projects UseReverseProxy forwards page requests; UseNuxtIntegrated is for SPA/static only
builder.UseViteDevProxy(o => o.DevServerUrl = "http://127.0.0.1:3000");
builder.UseReverseProxy(o => o.Routes.Add(new ProxyRoute
{
PathPrefix = "/",
Destination = "http://127.0.0.1:3000",
ExcludedPrefixes = ["/api"]
}));
Serves a pre-built Nuxt SPA from .output/public with tiered cache headers, Brotli/GZip compression, and SPA fallback.
builder.UseNuxtIntegrated(
outputPath: "frontend/.output/public",
configureFallback: o => o.ExcludedPrefixes = ["/api", "/health"]);
builder.UseViteFrontend(o =>
{
o.HtmlTemplatePath = "frontend/index.html";
o.ManifestPath = "wwwroot/.vite/manifest.json";
o.ExcludedPrefixes = ["/api", "/health"];
});
// With server-provided initial state
builder.UseViteFrontend(o =>
{
o.RenderAsync = ctx => ValueTask.FromResult<ViteRenderResult?>(new ViteRenderResult
{
HeadHtml = "<title>Dashboard</title>",
InitialState = new { user = "alice", route = ctx.HttpContext.Request.Path }
});
});
Generates a per-request nonce and injects it into inline scripts emitted by ViteFrontendMiddleware.
builder.UseCsp(o =>
{
o.DefaultSrc = ["'self'"];
o.ScriptSrc = ["'self'", "'nonce-{nonce}'"];
o.StyleSrc = ["'self'", "'unsafe-inline'"];
o.ConnectSrc = ["'self'"];
o.ImgSrc = ["'self'", "data:"];
});
Dev mode — Vite runs on port 5173 by default:
builder.UseViteDevServer(o =>
{
o.WorkingDirectory = "frontend";
o.Command = "npm";
o.Arguments = "run dev";
o.ReadyPattern = "Local:";
o.LogPrefix = "[react]";
});
builder.UseReactDevProxy(); // proxies /@vite, /@fs, /@id, /@react-refresh
builder.UseViteFrontend(o =>
{
o.EntryName = "src/main.tsx";
o.DevServerUrl = "http://127.0.0.1:5173";
o.ExcludedPrefixes = ["/api", "/health"];
});
Production — vite build outputs to dist/ with a manifest:
builder.UseViteFrontend(o =>
{
o.ManifestPath = "frontend/dist/.vite/manifest.json";
o.HtmlTemplatePath = "frontend/index.html";
o.EntryName = "src/main.tsx";
o.ExcludedPrefixes = ["/api", "/health"];
});
Dev mode — Angular CLI doesn't use Vite, so all traffic is reverse-proxied:
builder.UseAngularDevServer(o =>
{
o.WorkingDirectory = "frontend";
// defaults: npx ng serve --host 127.0.0.1, ready pattern "Application bundle generation complete"
});
builder.UseAngularDevProxy(); // reverse-proxies / → http://127.0.0.1:4200
Production — ng build outputs to dist/<project>/browser/:
builder.UseAngularFrontend(
outputPath: "frontend/dist/my-app/browser",
configureFallback: o => o.ExcludedPrefixes = ["/api", "/health"]);
Dev mode — Next.js uses its own HMR paths:
builder.UseNextDevServer(o =>
{
o.WorkingDirectory = "frontend";
// defaults: npm run dev, ready pattern "Ready"
});
builder.UseNextDevProxy(); // proxies /__next, /_next, /webpack-hmr
builder.UseViteFrontend(o =>
{
o.DevServerUrl = "http://127.0.0.1:3000";
o.ExcludedPrefixes = ["/api", "/health"];
});
Production (static export) — add output: 'export' to next.config.js, then next build outputs to out/:
builder.UseNextStaticExport(
outputPath: "frontend/out",
configureFallback: o => o.ExcludedPrefixes = ["/api", "/health"]);
Production (SSR) — proxy all non-API traffic to next start:
builder.UseReverseProxy(o =>
o.Routes.Add(new ProxyRoute
{
PathPrefix = "/",
Destination = "http://127.0.0.1:3000",
ExcludedPrefixes = ["/api", "/health"]
}));
Dev mode — SvelteKit uses Vite:
builder.UseViteDevServer(o =>
{
o.WorkingDirectory = "frontend";
o.Command = "npm";
o.Arguments = "run dev";
o.ReadyPattern = "Local:";
o.LogPrefix = "[svelte]";
});
builder.UseViteDevProxy(o =>
{
o.DevServerUrl = "http://127.0.0.1:5173";
o.ProxiedPrefixes = ["/@vite", "/@fs", "/@id", "/@svelte-kit"];
});
Production (adapter-static) — vite build outputs to build/:
builder.UseSvelteKitStatic(
outputPath: "frontend/build",
configureFallback: o => o.ExcludedPrefixes = ["/api", "/health"]);
Production (adapter-node) — proxy to the Node server:
builder.UseReverseProxy(o =>
o.Routes.Add(new ProxyRoute
{
PathPrefix = "/",
Destination = "http://127.0.0.1:3000",
ExcludedPrefixes = ["/api", "/health"]
}));
Blazor WASM runs entirely in the browser — the .NET runtime and your assemblies are compiled to WebAssembly and downloaded once. CosmoApiServer hosts the published output as static files with two additions over a plain SPA:
application/wasm MIME type — browsers reject WASM modules served as application/octet-stream_framework/ file serving — Blazor's publish pipeline emits .br and .gz variants of every framework file. Streaming these directly (with Content-Encoding: br) is far faster than re-compressing dotnet.native.wasm (30–60 MB) on every requestSetup
blazor/wwwroot folder of your CosmoApiServer project:dotnet publish BlazorApp/BlazorApp.csproj -c Release -o blazor
builder.UseBlazorWasm(
outputPath: "blazor/wwwroot",
configureFallback: o => o.ExcludedPrefixes = ["/api"]);
The SPA fallback returns index.html for all routes not matched by the API, enabling Blazor's client-side router.
Project structure
Keep the Blazor client project in a subdirectory (e.g. BlazorClient/) of the server project. Because Microsoft.NET.Sdk includes all *.cs files recursively by default, you must exclude the client's source files from the server build:
<ItemGroup>
<Compile Remove="BlazorClient\**\*.cs" />
</ItemGroup>
Without this, the server compiler picks up Blazor client code (e.g. WebAssemblyHostBuilder) and fails with CS0234.
Co-hosting API + Blazor WASM
var app = builder.Build();
app.MapGet("/api/weather", ctx => { ... });
// Blazor WASM handles everything else
Blazor calls your API endpoints the same way any SPA would — using HttpClient with a base address pointing at the same origin.
| Framework | Dev server default port | Paths to proxy |
|---|---|---|
| Nuxt | 3000 | /@vite /@fs /@id /_nuxt /__nuxt /__vite_ping |
| React + Vite | 5173 | /@vite /@fs /@id /@react-refresh |
| SvelteKit | 5173 | /@vite /@fs /@id /@svelte-kit |
| Next.js | 3000 | /__next /_next /__nextjs_original-stack-frame /webpack-hmr |
| Angular | 4200 | Full reverse proxy (no Vite module graph) |
builder.UseReverseProxy(o =>
o.Routes.Add(new ProxyRoute
{
PathPrefix = "/",
Destination = "http://127.0.0.1:3000",
ExcludedPrefixes = ["/api", "/health"]
}));
MapSse sets text/event-stream headers and calls BeginSseAsync automatically.
app.MapSse("/api/live/metrics", async ctx =>
{
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));
while (!ctx.RequestAborted.IsCancellationRequested &&
await timer.WaitForNextTickAsync(ctx.RequestAborted))
{
var json = JsonSerializer.Serialize(new { cpu = 42.1, memory = 68.3 });
await ctx.Response.WriteSseAsync(json, eventName: "metric",
cancellationToken: ctx.RequestAborted);
}
});
Heartbeat helper prevents proxies from closing idle connections:
var heartbeat = ctx.Response.SendSseHeartbeatsAsync(TimeSpan.FromSeconds(15), ctx.RequestAborted);
// ... event loop ...
await heartbeat;
var builder = CosmoWebApplicationBuilder.Create().AddSignalR();
var app = builder.Build();
app.MapHub<ChatHub>("/chat");
public sealed class ChatHub : Hub
{
public async Task SendMessage(string user, string message) =>
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
Supports JSON and MessagePack protocols, groups, server-push via IHubContext<T>, server streaming, cancellation, and reconnect-after-restart. Compatible with standard ASP.NET SignalR clients.
var builder = CosmoWebApplicationBuilder.Create().AddGrpc();
var app = builder.Build();
app.MapGrpcService<GreeterService>();
public sealed class GreeterService : GrpcServiceBase, IGrpcServiceDescriptor
{
public string ServiceName => "Greeter";
public IReadOnlyList<GrpcMethodDescriptor> Methods =>
[
new GrpcMethodDescriptor("Greeter", "SayHello", GrpcMethodType.Unary, typeof(GreeterService),
(svc, ctx, ct) =>
{
var rpc = new GrpcUnaryContext<HelloRequest, HelloReply>(ctx);
rpc.WriteResponse(new HelloReply { Message = $"Hello {rpc.Request.Name}" });
return Task.CompletedTask;
})
];
}
HelloRequest/HelloReply are Protobuf messages (IMessage<T>). GrpcUnaryContext decodes the
request from the standard 5-byte gRPC framing (rpc.Request) and writes the response
(rpc.WriteResponse). Supports unary and server-streaming.
builder
.UseJwtAuthentication(o => o.Secret = "your-signing-secret-at-least-32-bytes")
.UseOAuthAuthentication(o => o.Authority = "https://issuer.example.com") // JWKS discovery
.AddAuthorization();
builder.AddAntiforgery();
// ...
app.UseAntiforgery();
app.MapGet("/form", ctx =>
{
var svc = ctx.RequestServices.GetRequiredService<IAntiforgeryService>();
var tokens = svc.GetAndStoreTokens(ctx);
return TypedResults.Text($"<input name='__RequestVerificationToken' value='{tokens.RequestToken}' />");
});
Fixed-window per-IP limiter with X-Forwarded-For support:
builder.UseRateLimiting(opts =>
{
opts.Limit = 200;
opts.Window = TimeSpan.FromMinutes(1);
opts.StatusCode = 429;
opts.TrustProxy = true;
});
Request
↓ GlobalExceptionHandlerMiddleware
↓ CorsMiddleware
↓ CspMiddleware
↓ ViteDevProxyMiddleware (dev)
↓ ViteFrontendMiddleware (dev) / NuxtIntegratedMiddleware (prod)
↓ RouterMiddleware
Registration follows a builder pattern:
var builder = CosmoWebApplicationBuilder.Create()
.ListenOn(9092)
.UseLogging()
.UseExceptionHandler()
.UseCors(o => { o.AllowAnyOrigin(); o.AllowAnyMethod(); o.AllowAnyHeader(); })
.UseSession(o => o.IdleTimeout = TimeSpan.FromMinutes(20))
.UseRequestTimeouts(o => o.DefaultTimeout = TimeSpan.FromSeconds(30))
.UseRateLimiting(opts => { opts.Limit = 100; opts.Window = TimeSpan.FromMinutes(1); })
.UseForwardedHeaders()
.AddOutputCache()
.AddHealthChecks();
app.MapGet("/items/{id}", ctx => { ... });
app.MapPost("/items", ctx => { ... });
// Typed results
app.MapGet("/items/{id}", ctx =>
id is null ? TypedResults.NotFound() : TypedResults.Ok(new { id, name = "Widget" }));
builder
.AddExceptionHandler<ValidationExceptionHandler>()
.AddExceptionHandler<DatabaseExceptionHandler>();
public sealed class ValidationExceptionHandler : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(HttpContext ctx, Exception ex, CancellationToken ct)
{
if (ex is not ValidationException vex) return false;
ctx.Response.StatusCode = 422;
await ctx.Response.WriteJsonAsync(new { errors = vex.Errors }, ct);
return true;
}
}
builder.AddScheduler();
// ...
app.UseScheduler(scheduler =>
{
scheduler.Schedule(() => Console.WriteLine("tick")).EveryMinute();
scheduler.ScheduleAsync(async () => await SyncInvoices()).Cron("0 */1 * * *");
scheduler.Schedule<CleanupJob>().DailyAt(2, 30);
});
builder.AddOutputCache();
builder.UseOutputCaching();
var app = builder.Build();
var policy = OutputCachePolicy.Build()
.Expire(TimeSpan.FromMinutes(5))
.VaryByQuery("page", "sort")
.Tag("products")
.ToPolicy();
app.MapGet("/products", async ctx =>
{
ctx.SetOutputCachePolicy(policy);
await ctx.Response.WriteJsonAsync(GetProducts());
});
// Tag-based invalidation
var store = ctx.RequestServices.GetRequiredService<IOutputCacheStore>();
await store.EvictByTagAsync("products");
builder.UseResponseCaching();
// Handlers set ctx.Response.Headers["ETag"]; returns 304 when If-None-Match matches.
builder
.AddMemoryCache()
.AddDistributedMemoryCache();
builder.AddHealthChecks()
.AddCheck("db", () => HealthCheckResult.Healthy("Connected"))
.AddCheck<MyDbHealthCheck>("database");
builder.UseHealthChecks("/health"); // serves the aggregated report at /health
builder.AddProblemDetails();
app.MapGet("/items/{id}", async ctx =>
{
var svc = ctx.RequestServices.GetRequiredService<IProblemDetailsService>();
await svc.WriteAsync(new ProblemDetailsContext
{
HttpContext = ctx,
ProblemDetails = new ProblemDetails { Status = 404, Title = "Not Found" }
});
});
W3C traceparent propagation, OpenTelemetry-compatible ActivitySource:
builder.UseTracing(o => o.ServiceName = "MyService");
Full .razor support with @page, [Parameter], [CascadingParameter], @inject, @bind, EventCallback, and validation components.
@page "/hello/{Name}"
@inherits Microsoft.AspNetCore.Components.ComponentBase
@inject NavigationManager Nav
<h1>Hello, @Name!</h1>
<p>Path: @Nav.Uri</p>
@code {
[Parameter] public string Name { get; set; }
}
<EditForm Model="@person" Action="/contact" Method="post">
<InputText Name="name" Value="@person.Name" />
<InputNumber Name="age" Value="@person.Age" />
<InputSelect Name="country" Value="@person.Country">
<option value="us">United States</option>
</InputSelect>
<ValidationMessage For="Name" />
<ValidationSummary />
<button type="submit">Submit</button>
</EditForm>
var ctx = new EditContext(model);
model.Name = "Bob";
ctx.NotifyFieldChanged("Name");
ctx.IsModified("Name"); // true
ctx.GetModifiedFields(); // ["Name"]
ctx.FieldCssClass("Name"); // "modified valid"
ctx.MarkAsUnmodified();
| Sample | Description |
|---|---|
samples/HelloWorldSample |
Minimal server with a single route |
samples/CosmoKitchenSink |
Covers most backend features in one project |
samples/FeatureShowcase |
Auth, SignalR, gRPC, output cache, and more |
samples/WeatherApp |
REST API with JWT auth, DI, streaming, and SQL |
samples/NuxtUiSample |
Nuxt 4 + Nuxt UI frontend backed by Cosmo APIs |
samples/LiveOpsSample |
Real-time dashboard: SSE, CSP, Vite dev server, Nuxt integrated deployment |
samples/CosmoBlazorSample |
SSR with Razor components |
samples/BlazorWasmSample |
Blazor WebAssembly co-hosted with a CosmoApiServer API |
cd samples/BlazorWasmSample
./run.sh
Publishes the Blazor WASM client to blazor/wwwroot, then starts the CosmoApiServer backend on http://localhost:5050. Includes Notes (CRUD via /api/notes), Counter, and Weather pages.
cd samples/LiveOpsSample
npm run frontend:install
npm run dev
Benchmarks:
npm run benchmark # API-only latency (no Nuxt)
npm run benchmark:nuxt # Cosmo integrated vs Nuxt Nitro standalone
cd samples/NuxtUiSample
npm run dev
Single-client, sequential requests over one reused connection — 100 warmup + 1000 measured per
scenario. This measures per-request latency, not concurrent throughput (which would differ).
Run with ./run_benchmark.sh; the load driver is tests/ApiServer.Benchmark.
Captured 2026-06-12 on a Debian 12 server (x86-64, bare metal, .NET 10, libmsquic for HTTP/3),
CosmoApiServer vs ASP.NET Core (Kestrel) on the identical handlers. P50 latency (ms) and
operations/second; lower latency / higher ops is better.
| Scenario | Cosmo P50 | Cosmo ops/s | Kestrel P50 | Kestrel ops/s |
|---|---|---|---|---|
| GET /ping | 0.10 | 9,911 | 0.12 | 8,039 |
| GET /json | 0.10 | 10,406 | 0.12 | 8,599 |
| GET /route/{id} | 0.11 | 9,470 | 0.11 | 9,434 |
| POST /echo | 0.11 | 9,425 | 0.11 | 9,434 |
| GET /large-json | 0.96 | 1,046 | 1.01 | 994 |
| GET /query | 0.08 | 12,453 | 0.10 | 9,766 |
| POST /form | 0.09 | 11,601 | 0.11 | 9,363 |
| GET /headers | 0.09 | 11,455 | 0.10 | 10,020 |
| GET /stream | 0.15 | 6,553 | 0.09 | 10,707 |
| GET /file (64 KiB) | 0.16 | 6,072 | 0.20 | 4,943 |
CosmoApiServer leads or ties on most scenarios. GET /file serves hot files (≤256 KiB) from a
bounded in-memory cache; larger files stream in 64 KiB chunks. GET /stream flushes per line.
(The /file and /stream rows above predate the 2026-06 concurrency work — those paths were
since reworked; the table below is the current, authoritative comparison.)
Latency ≠ throughput. This table is single-connection latency. For server capacity, what matters is concurrent throughput, measured separately below — and the two can disagree sharply (an early
sendfilechange looked like a/filewin here while being an 8× concurrency regression). Always judge transport/streaming changes under concurrency.
wrk -t4 -c64 -d10s, keep-alive, identical handlers, Debian / 12-core / .NET 10, warm
steady-state requests/sec (higher is better) and P99 latency (lower is better). Reproduce with
.
| Scenario | Cosmo req/s | Kestrel req/s | Cosmo/Kestrel | Cosmo P99 | Kestrel P99 |
|---|---|---|---|---|---|
| GET /ping | 158k | 136k | 1.17× | 8.0ms | 10.5ms |
| GET /json | 160k | 142k | 1.13× | 8.2ms | 9.3ms |
| GET /route/{id} | 162k | 138k | 1.18× | 8.7ms | 14.4ms |
| GET /large-json | 14.2k | 13.1k | 1.08× | 15ms | 77ms |
| GET /stream | 100k | 130k | 0.77× | 9.1ms | 67ms |
| GET /file (64 KiB) | 32k | 39k | 0.83× | 12.7ms | 58ms |
Cosmo wins throughput on the small-payload API paths and wins P99 tail latency on every
scenario — markedly so on the large/streaming paths (5–7× tighter than Kestrel). Kestrel
still leads raw /stream and /file throughput via its socket-native output (Cosmo wraps a
NetworkStream), but the gaps closed substantially after the 2026-06 concurrency work:
/stream ~2× (drained output pipe) and /file recovered from a 0.59× regression to ~0.83×
(small-file cache). See .
| Scenario | P50 | P99 | ops/s |
|---|---|---|---|
| GET /ping | 0.21 | 2.52 | 4,708 |
| GET /json | 0.26 | 2.32 | 3,882 |
| GET /route/{id} | 0.26 | 3.15 | 3,881 |
| POST /echo | 0.19 | 3.38 | 5,181 |
| GET /large-json | 1.21 | 11.84 | 824 |
| GET /query | 0.17 | 4.32 | 5,790 |
| POST /form | 0.19 | 4.37 | 5,147 |
| GET /headers | 0.18 | 0.25 | 5,705 |
| GET /stream | 0.24 | 3.60 | 4,139 |
| GET /file (64 KiB) | 0.34 | 3.26 | 2,929 |
HTTP/3 carries higher per-request overhead than plaintext HTTP/1.1 in this single-connection sequential test (QUIC stream setup + crypto). Its real advantages — no head-of-line blocking under loss, and independent per-stream flow control — show under concurrency and lossy networks, which this latency benchmark does not exercise.
Add UseHttp3() with a valid certificate:
var builder = CosmoWebApplicationBuilder.Create()
.ListenOn(9092)
.UseHttps("cert.pfx", "password")
.UseHttp3();
When running Nuxt through UseNuxtIntegrated, all assets — HTML, JS, CSS — are served by Cosmo over the same QUIC connection. SSE streams benefit from QUIC's per-stream flow control, which avoids head-of-line blocking between concurrent requests.
Nuxt can be deployed to Cloudflare using the cloudflare_pages preset:
npx nuxi build --preset=cloudflare_pages
This gives HTTP/3 at the Cloudflare edge automatically. However, there are trade-offs relevant to this architecture:
| Cloudflare | Cosmo + UseHttp3() |
|
|---|---|---|
| HTTP/3 on browser leg | Yes (edge) | Yes (direct) |
| SSE streams | Fragile — 100s connection timeout on free plan | Native |
| API colocation | No — /api/* still needs an origin server |
Yes |
| Cloudflare → origin leg | HTTP/2 at best | N/A |
Cloudflare is suitable for frontends that are mostly static or read-heavy without persistent connections. For SSE-heavy or API-coupled deployments, UseHttp3() on Cosmo keeps the entire stack on one connection and one protocol.
.cshtml view code references
types the framework's compile-time stubs didn't cover, which broke every RazorSlice project.
Added stubs (IModelExpressionProvider, RazorCompiledItem/RazorCompiledItemMetadata/
ProvideApplicationPartFactory attributes) so .cshtml slices compile without the ASP.NET MVC
framework again.Security, protocol-correctness, and performance release.
RequireAuthorization bypass (filters were silently
dropped), fail-open [Authorize] (now fails closed), output/response cache session poisoning
(no longer caches authenticated / Set-Cookie / no-store responses; keys on Host), stored
XSS in HtmlEditorComponent (encoded by default), JWT access_token query param accepted on
every request (now WebSocket-upgrade only), component-POST mass-assignment ([BindProperty]
allow-list + antiforgery), and request-smuggling vectors (strict Content-Length / Transfer-
Encoding, chunked-trailer handling, percent-decoded routing, proxy hop-by-hop + surplus-byte
handling).ServerOptions:
MaxConcurrentConnections, MaxRequestHeaderSize, MaxRequestHeaderCount, Http3MaxFrameSize.sendfile for whole-file responses on plaintext HTTP/1.1; buffered
JSON serialized directly into the response buffer; idle-vs-active connection timeouts so
streaming/SSE/WebSocket connections aren't dropped while active; scheduler/cron and OpenAPI
generator fixes.MessagePack bumped to 2.5.301 (clears advisory GHSA-hv8m-jj95-wg3x).BlazorWasmSample — new sample: Notes CRUD + Counter + Weather, Blazor WASM co-hosted with CosmoApiServerUseBlazorWasm project structure: document <Compile Remove="BlazorClient\**\*.cs" /> requirement when client lives in a subdirectory of the server projectUseBlazorWasm — hosts published Blazor WebAssembly apps with pre-compressed _framework/ file serving and application/wasm MIME typeStaticFileMiddleware — added .wasm → application/wasm to the MIME tableUseStaticFrontend(outputPath) — generic base for all static SPA deploymentsUseAngularFrontend, UseNextStaticExport, UseSvelteKitStatic — framework-specific wrappersUseReactDevProxy, UseNextDevProxy, UseAngularDevProxy — pre-configured dev proxies per frameworkUseAngularDevServer, UseNextDevServer — pre-configured dev server process managementrun-benchmark.sh, run-nuxt-benchmark.sh)samples/LiveOpsSample demonstrating all six featuresRetry-After off-by-one, ForwardedHeaders trusted-proxy gate, AntiforgeryMiddleware Content-Type guard, SPA fallback cache-controlViteFrontendMiddlewaresamples/NuxtUiSampleHtmlEditorComponent for Razor slicesInputDate, InputRadioGroup, InputRadio, InputFile, RenderTreeBuilder stubsIHttpContextAccessorIExceptionHandler, IHostedService, WebSocketsIHttpClientFactory| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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 1 NuGet packages that depend on CosmoApiServer.Core:
| Package | Downloads |
|---|---|
|
CosmoS3
Amazon S3-compatible object storage server built on CosmoApiServer.Core. |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.8.1 | 111 | 6/16/2026 |
| 3.8.0 | 183 | 6/14/2026 |
| 3.7.0 | 150 | 6/13/2026 |
| 3.6.0 | 106 | 6/13/2026 |
| 3.5.1 | 93 | 6/12/2026 |
| 3.5.0 | 90 | 6/12/2026 |
| 3.4.8 | 132 | 6/7/2026 |
| 3.4.7 | 109 | 6/2/2026 |
| 3.4.6 | 95 | 6/2/2026 |
| 3.4.5 | 95 | 6/2/2026 |
| 3.4.4 | 104 | 6/2/2026 |
| 3.4.3 | 114 | 5/29/2026 |
| 3.4.2 | 100 | 5/29/2026 |
| 3.4.1 | 102 | 5/29/2026 |
| 3.4.0 | 214 | 5/23/2026 |
| 3.3.25 | 107 | 5/13/2026 |
| 3.3.24 | 154 | 5/11/2026 |
| 3.3.23 | 95 | 5/11/2026 |
| 3.3.22 | 101 | 5/10/2026 |
| 3.3.21 | 99 | 5/10/2026 |