deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

vite-ssr-boost now 404s /.env-style requests before the SSR render pipeline runs

vite-ssr-boost's latest release enables a document guard by default, returning plain 404s for probe paths like /.env before the SSR request hook or render pipeline runs. An SSR concurrency limit joins it as opt-in.

vite-ssr-boost now 404s /.env-style requests before the SSR render pipeline runs

A guard that runs first

The latest release of vite-ssr-boost ships with a document guard enabled by default, and its job is simple: decide whether an incoming request deserves a React tree at all. According to a dev.to post announcing the release, a GET for /.env or /random.php now returns a plain 404 without rendering anything, because those are paths scanners commonly probe rather than pages a real visitor would request.

The guard runs before the framework's onRequest hook, before HTML is loaded and before route loaders execute. Requests using methods outside the default set of GET, HEAD and POST receive a 405 with an Allow header at the same early stage. For allowed methods, oversized targets get a 414 and malformed paths a 400. Unmatched paths such as /missing.xml are also rejected, although a matched resource route like /sitemap.xml can still pass.

One caveat the post highlights: if your request hook handles CORS preflights, those OPTIONS requests will now be turned away unless you add OPTIONS to requestGuard.methods, an array that replaces the defaults. Setting requestGuard: false disables the guard and its missing-page handling entirely, and the authors note this is document-handler behavior — it does not cover every request that reaches your server.

Configuring what a missing page does

For an unmatched document such as /missing, the new notFound option offers several modes. The default, render, keeps the existing router-and-render path. spa serves a client shell with a 404 status, with detected bots still taking the render path under the default bot policy. A custom Response produces a static 404 without invoking the render pipeline at all.

A catch-all route counts as a match, so to apply a missing-page mode inside one you return 'notFound' from requestGuard.decide.

There is also a cached mode, which buffers a router 404 and reuses it while the entry is retained. Concurrent misses for the same key share a single render, and subsequent hits skip onRequest and the whole render pipeline, loaders included. The default key is shared across all missing paths and includes the first rendered URL and hydration data; cold renders use GET without the original body, with Cookie and Authorization stripped before the request hook. The post warns that the URL, other headers and application state can still influence the output, so private state must stay out of shared HTML, and pages that depend on a session should stick to ordinary rendering. A configured CSP nonce disables the cache and falls back to rendering, while failed renders and non-404 results are never retained. The cached document carries a private, no-store header by default, and this cache is separate from browser or CDN caching.

Opt-in render admission

The second headline feature, an SSR concurrency limit, is off by default. Setting admission.maxConcurrency to a positive safe integer — or providing SSR_MAX_CONCURRENCY, which takes precedence and is read at handler or entry creation — enables it. The post describes it as handler-local rather than a cluster-wide cap.

A slot is taken after the SSR/SPA decision but before route loaders run. At capacity the default response is 503 with Retry-After and private, no-store headers; there is no queueing. Because onRequest and HTML loading have already happened by that point, those hooks still execute even for rejected requests.

With admission.overload set to 'spa', a full admission controller returns a 200 shell for humans and a 503 for detected bots — not to be confused with the missing-page SPA mode, which serves its shell with a 404. A custom overload Response is always sent as 503. On streamed responses the slot stays occupied until the final Fetch response stream is consumed, not merely until React emits a shell; aborts, errors, redirects and bodyless responses have their own release paths. Ordinary SPA shells and cached 404 hits never occupy a slot.

Why it matters

Automated scanners probe /.env, stray PHP files and similar paths continuously, and in a conventional SSR setup each of those requests can trigger a full render — wasted CPU and memory, and application code running for a request that should never have been entertained. Rejecting those requests before any hook or loader runs is cheap defense in depth, and making it the default is the sensible call for a framework. The trade-off is behavioral: code that assumed onRequest always fires — for CORS preflights, auth or logging — needs rechecking, and the post recommends explicitly testing a preflight that must reach your hook. The optional 404 cache and concurrency admission round out the release as practical protection against probe traffic and bursts, provided shared cached pages never contain private data.

  • #vite
  • #vite-ssr-boost
  • #ssr
  • #web-security
  • #react

Related posts