Skip to content

feat: serve Vue SPA as static files from .NET API (PKG-01 #533)#556

Merged
Chris0Jeky merged 3 commits intomainfrom
feat/pkg-01-spa-static-serving
Mar 29, 2026
Merged

feat: serve Vue SPA as static files from .NET API (PKG-01 #533)#556
Chris0Jeky merged 3 commits intomainfrom
feat/pkg-01-spa-static-serving

Conversation

@Chris0Jeky
Copy link
Copy Markdown
Owner

Summary

  • Adds UseDefaultFiles(), UseStaticFiles(), and MapFallbackToFile("index.html") to PipelineConfiguration.cs
  • Enables the packaged .NET API to serve the Vue SPA from wwwroot/ without a separate web server
  • API routes (prefixed /api/*) and SignalR hubs (/hubs/*) are unaffected by the HTML fallback

Closes #533

Test plan

  • dotnet build Taskdeck.sln -c Release passes
  • All backend tests pass
  • When running the API with built frontend in wwwroot/, navigating to the app root serves index.html
  • API endpoints still respond correctly (not intercepted by static file middleware)

Add UseDefaultFiles(), UseStaticFiles(), and MapFallbackToFile("index.html")
to PipelineConfiguration so the packaged .NET API can serve the built Vue SPA
from wwwroot/ without a separate web server. API and SignalR hub routes are
matched before the fallback and remain unaffected.
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request configures the ASP.NET Core pipeline to serve a Vue SPA by adding static file middleware and a fallback route for client-side navigation. A security concern was raised regarding the middleware order: UseStaticFiles is currently placed before SecurityHeadersMiddleware, which prevents security headers from being applied to the SPA's static assets. It is recommended to move the security middleware earlier in the pipeline to ensure all responses are protected.

Comment on lines +51 to +52
app.UseDefaultFiles();
app.UseStaticFiles();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current placement of the static files middleware can lead to security issues. The UseStaticFiles middleware can short-circuit the request pipeline, meaning any middleware registered after it will not run for static file requests.

Currently, SecurityHeadersMiddleware is registered after UseStaticFiles. This means important security headers like Content-Security-Policy, X-Frame-Options, and X-ContentType-Options will not be applied to the SPA's main index.html file or its assets (JS, CSS). This could expose your application to vulnerabilities like clickjacking or weaken its defense against XSS.

To ensure all responses receive these critical security headers, you should reorder the middleware pipeline. I recommend moving app.UseMiddleware<SecurityHeadersMiddleware>(); to before the app.UseDefaultFiles(); call. You might also consider moving app.UseMiddleware<CorrelationIdMiddleware>(); for better request tracing.

Move UseDefaultFiles()/UseStaticFiles() to after SecurityHeadersMiddleware so
the OnStarting callback is registered before the pipeline short-circuits,
ensuring CSP, X-Frame-Options, and other security headers are applied to SPA
assets including index.html.
@Chris0Jeky
Copy link
Copy Markdown
Owner Author

Self-review findings and fix applied:

Issue found: Original placement of UseDefaultFiles()/UseStaticFiles() was before SecurityHeadersMiddleware. Because UseStaticFiles short-circuits the pipeline, the Response.OnStarting callback that SecurityHeadersMiddleware registers to inject CSP, X-Frame-Options, and other headers would never be registered for static file requests — meaning index.html and all SPA assets would be served without security headers.

Fix applied (commit 011f0c05): Moved the static file middleware to after SecurityHeadersMiddleware. Since SecurityHeadersMiddleware uses Response.OnStarting (lazy registration before headers flush), it correctly fires even when UseStaticFiles short-circuits the downstream pipeline, so security headers are now applied to all responses including SPA assets.

Remaining checks passed:

  • Middleware ordering: CORS → correlation/exception/security headers → DefaultFiles → StaticFiles → auth → rate limiting → authorization → controllers → hub → fallback. Correct.
  • Swagger: registered as endpoint above, not caught by MapFallbackToFile. Safe.
  • SignalR /hubs/boards: registered as endpoint, takes precedence. Safe.
  • Directory listing: not enabled (no DirectoryBrowser registered). Safe.
  • All 1485 backend tests pass after the fix.

LGTM after fix.

Issue AC specifies "SPA assets are served with appropriate cache headers".
Vite-generated /assets/* files use content-hashed names so they can be
cached indefinitely (max-age=31536000, immutable). All other files,
including index.html, get no-cache to ensure users receive the latest
version after each deployment.
@Chris0Jeky
Copy link
Copy Markdown
Owner Author

Adversarial Review Pass 2

Checks run: dotnet build (0 warnings, 0 errors), dotnet test (1,485 tests, 0 failures across all suites: Domain.Tests, Application.Tests, Api.Tests, Cli.Tests, Architecture.Tests)

Findings:

  1. Middleware ordering — CORRECT. Order is: CORS → CorrelationId → UnhandledException → SecurityHeaders → DefaultFiles → StaticFiles → Authentication → RateLimiter → Authorization → Controllers → Hub → FallbackToFile. Security middleware runs before static files.

  2. SecurityHeadersMiddleware + UseStaticFiles short-circuit — CORRECT. SecurityHeadersMiddleware uses Response.OnStarting(...) (line 24 of SecurityHeadersMiddleware.cs). This callback is registered before await _next(context) is called, so it is already in the OnStarting chain when UseStaticFiles short-circuits and writes the response. Security headers (CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy) are applied to all static file responses including index.html.

  3. Swagger routes — SAFE. UseSwagger() and UseSwaggerUI() are branch middleware (not endpoints). They evaluate the path early in the middleware pipeline and short-circuit for /swagger before endpoint routing fires. MapFallbackToFile is endpoint-level and is never reached for Swagger paths.

  4. SignalR /hubs/* — SAFE. MapHub<BoardsHub>("/hubs/boards") is registered as an endpoint before the fallback. Endpoint routing evaluates exact matches before the fallback pattern.

  5. API routes (/api/*) — SAFE. All controllers use [Route("api/...")] consistently. MapControllers is registered before MapFallbackToFile.

  6. wwwroot path — Default IWebHostEnvironment.WebRootPath (resolved relative to ContentRootPath). No wwwroot directory exists yet; UseStaticFiles and MapFallbackToFile handle a missing/empty wwwroot gracefully (404 rather than a crash). The build pipeline for copying dist/ to wwwroot/ is tracked in PKG-02 (PKG-02: Create unified build script for self-contained single-file publish #534) which is already open.

  7. index.html Content-Type — Correct. ASP.NET Core's FileExtensionContentTypeProvider maps .htmltext/html; charset=utf-8. No issue.

  8. Directory browsing — DISABLED. No app.UseDirectoryBrowser() call anywhere. StaticFileOptions does not expose DirectoryBrowserOptions. Safe.

  9. Cache headers (MISSING AC — FIXED) — Issue PKG-01: Add SPA static file serving to ASP.NET Core for self-contained deployment #533 AC explicitly states "SPA assets are served with appropriate cache headers." The original PR did not set any Cache-Control headers. Fix applied: UseStaticFiles now uses StaticFileOptions.OnPrepareResponse to emit Cache-Control: public, max-age=31536000, immutable for /assets/* (Vite content-hashed filenames) and Cache-Control: no-cache for everything else including index.html.

  10. Scope — The PR is tightly scoped to PipelineConfiguration.cs only. No out-of-scope changes.

Fixes applied:

  • fix: add cache-control headers for SPA static assets (PKG-01 AC) — commit 5200b6b4. Implements appropriate Cache-Control strategy: long-lived immutable caching for Vite-hashed assets, no-cache for index.html and other non-versioned files.

Overall verdict: LGTM after fix. The middleware ordering is correct, security headers are properly applied to static file responses via the OnStarting mechanism, and the SPA fallback is safely scoped behind all real API/hub endpoint registrations.

@Chris0Jeky Chris0Jeky merged commit a35a452 into main Mar 29, 2026
18 checks passed
@Chris0Jeky Chris0Jeky deleted the feat/pkg-01-spa-static-serving branch March 29, 2026 18:18
@github-project-automation github-project-automation bot moved this from Pending to Done in Taskdeck Execution Mar 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

PKG-01: Add SPA static file serving to ASP.NET Core for self-contained deployment

1 participant