<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Melissa Ashford</title>
    <description>The latest articles on DEV Community by Melissa Ashford (@melissa_lomray).</description>
    <link>https://dev.to/melissa_lomray</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4134517%2F5f0ff78c-36bc-4c3b-a3cc-d46e98c826d3.png</url>
      <title>DEV Community: Melissa Ashford</title>
      <link>https://dev.to/melissa_lomray</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/melissa_lomray"/>
    <language>en</language>
    <item>
      <title>A request for /.env shouldn't render your React app</title>
      <dc:creator>Melissa Ashford</dc:creator>
      <pubDate>Tue, 22 Sep 2026 01:17:49 +0000</pubDate>
      <link>https://dev.to/lomray-software/a-request-for-env-shouldnt-render-your-react-app-4j0p</link>
      <guid>https://dev.to/lomray-software/a-request-for-env-shouldnt-render-your-react-app-4j0p</guid>
      <description>&lt;p&gt;A request for &lt;code&gt;/.env&lt;/code&gt; doesn't need a React tree. In the latest release, vite-ssr-boost can reject it before your request hook runs.&lt;/p&gt;

&lt;p&gt;The document guard is on by default. The SSR concurrency limit isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your hook might not run
&lt;/h2&gt;

&lt;p&gt;The default document methods are GET, HEAD and POST. Other methods get 405 with an &lt;code&gt;Allow&lt;/code&gt; header before &lt;code&gt;onRequest&lt;/code&gt;, HTML loading or route loaders. If your hook handles CORS preflights, include OPTIONS in &lt;code&gt;requestGuard.methods&lt;/code&gt; alongside the methods you still need. That array replaces the defaults.&lt;/p&gt;

&lt;p&gt;Allowing a method doesn't bypass the other checks. For allowed methods, oversized targets get 414 and malformed paths get 400. With the default rules, GET requests for &lt;code&gt;/.env&lt;/code&gt; or &lt;code&gt;/random.php&lt;/code&gt; get a plain 404 without rendering. An unmatched &lt;code&gt;/missing.xml&lt;/code&gt; does too; a matched resource route such as &lt;code&gt;/sitemap.xml&lt;/code&gt; can pass.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;requestGuard: false&lt;/code&gt; disables the new guard and its missing-page handling. This is document-handler behavior, not protection for every request reaching your server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose what a missing page does
&lt;/h2&gt;

&lt;p&gt;For an unmatched document such as &lt;code&gt;/missing&lt;/code&gt;, &lt;code&gt;notFound&lt;/code&gt; defaults to &lt;code&gt;render&lt;/code&gt;: the existing router/render path. &lt;code&gt;spa&lt;/code&gt; serves a client shell with 404; detected bots still take the render path under the default bot policy. A custom Response gives you a static 404 without the render pipeline.&lt;/p&gt;

&lt;p&gt;A catch-all route counts as a match. To apply a missing-page mode there, return &lt;code&gt;'notFound'&lt;/code&gt; from &lt;code&gt;requestGuard.decide&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cached&lt;/code&gt; buffers a router 404 and reuses it while the entry is retained. Concurrent misses for the same key share a render. Hits skip &lt;code&gt;onRequest&lt;/code&gt; and the render pipeline, including loaders and admission. Don't depend on those hooks running for every missing page.&lt;/p&gt;

&lt;p&gt;The default key is shared across missing paths, including the first rendered URL and hydration data. Cold renders use GET without the original body, with Cookie and Authorization removed before the request hook. The URL, other headers and application state can still affect the result. Keep private state out of shared HTML; choose keys for public variations such as locale. If the page depends on a session, keep ordinary rendering instead.&lt;/p&gt;

&lt;p&gt;A configured CSP nonce disables this cache and falls back to ordinary rendering. Failed renders and non-404 results aren't retained. Overloads and server failures keep their error status; a request-hook bypass keeps its own response. The default &lt;code&gt;private, no-store&lt;/code&gt; header on 404 documents can be overridden by document header rules. This HTML cache is separate from browser or CDN caching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limit renders, not every bit of server work
&lt;/h2&gt;

&lt;p&gt;Admission is opt-in and handler-local, not a cluster-wide cap.&lt;/p&gt;

&lt;p&gt;Set a positive safe integer in &lt;code&gt;admission.maxConcurrency&lt;/code&gt;, or a valid &lt;code&gt;SSR_MAX_CONCURRENCY&lt;/code&gt;. The environment value wins and is read at handler/entry creation. No limit is enabled by default.&lt;/p&gt;

&lt;p&gt;A slot is taken after request initialization and the SSR/SPA decision, before route loaders. At capacity the default response is 503 with &lt;code&gt;Retry-After&lt;/code&gt; and &lt;code&gt;private, no-store&lt;/code&gt;. There is no admission queue. The rejected request's loaders don't run, but &lt;code&gt;onRequest&lt;/code&gt; and HTML loading have already happened.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;admission.overload: 'spa'&lt;/code&gt;, requests that reach a full admission controller get a 200 shell for humans and 503 for detected bots. That isn't the missing-page SPA mode, whose shell uses 404. A custom overload Response is sent as 503.&lt;/p&gt;

&lt;p&gt;On normal streamed completion, the slot stays occupied until the final Fetch response stream is consumed, not merely until React produces a shell. This doesn't mean the last byte has reached the browser. Aborts and errors can release it earlier; redirects and bodyless responses have their own release paths. Ordinary SPA shells and cached 404 hits don't occupy a slot.&lt;/p&gt;

&lt;p&gt;Before deploying, test a preflight that must reach your hook. Check that missing URLs don't share private data. For admission, keep one response stream open and send another SSR request at capacity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://lomray-software.github.io/vite-ssr-boost/api/request-guard" rel="noopener noreferrer"&gt;Configuration reference&lt;/a&gt;, &lt;a href="https://github.com/Lomray-Software/vite-ssr-boost/blob/db2b419e16bee8dfdc5c31577af1d21ada833cf5/__tests__/core/not-found.ts" rel="noopener noreferrer"&gt;guard and 404 tests&lt;/a&gt;, &lt;a href="https://github.com/Lomray-Software/vite-ssr-boost/blob/db2b419e16bee8dfdc5c31577af1d21ada833cf5/__tests__/core/admission.ts" rel="noopener noreferrer"&gt;admission tests&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>We deleted Vite from our production server</title>
      <dc:creator>Melissa Ashford</dc:creator>
      <pubDate>Mon, 21 Sep 2026 16:59:28 +0000</pubDate>
      <link>https://dev.to/lomray-software/we-deleted-vite-from-our-production-server-4n1l</link>
      <guid>https://dev.to/lomray-software/we-deleted-vite-from-our-production-server-4n1l</guid>
      <description>&lt;p&gt;vite-ssr-boost is an SSR framework built on Vite. Its production server used to start this way: load Vite, resolve vite.config, build the full CLI command tree, then serve the first request. None of that is needed to serve HTML.&lt;/p&gt;

&lt;p&gt;In 8.4.x we cut it out. The numbers below are our own runs on a pinned template, M3 Pro, installed through npm.&lt;/p&gt;

&lt;p&gt;What the production start used to do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;load Vite and evaluate vite.config&lt;/li&gt;
&lt;li&gt;build the whole Commander command tree just to parse the flags of &lt;code&gt;start&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;spin up the source analysis services that only dev mode uses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it does now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ssr-boost build&lt;/code&gt; writes a versioned &lt;code&gt;build/server/ssr-boost.json&lt;/code&gt; with the resolved serving settings. Production reads that file and never evaluates the Vite config.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ssr-boost start&lt;/code&gt; parses its flags with Node's own parser. Every other command loads on demand, keyboard shortcuts load only when there is a TTY.&lt;/li&gt;
&lt;li&gt;An import hook watches the serving path. It rejects vite, rolldown, rollup, commander, diff, parse5, semver, json5 and the babel packages, and it also rejects our own dev-only code: the cli commands, the build, manifest and route parsing services, the plugins. That is the part that keeps it honest. The hook catches accidental imports before they reach production.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it bought:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cold start 354 ms to 249 ms. A plain Express server with renderToPipeableStream is 171 ms on the same machine.&lt;/li&gt;
&lt;li&gt;RSS 142 MiB to 100 MiB. Same plain Express baseline: 69 MiB.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then the per-request path. React's native Web stream on React 19.2+, the production shell cached instead of rebuilt, boundary scanning only while data frames can still arrive, a plain state script for routes without router data, static dispatch by built prefixes so there is no filesystem lookup per HTML request, the header queued at shell readiness, compression flushed before drain.&lt;/p&gt;

&lt;p&gt;One more part, the one I would copy even if you skip everything else. Acceptance measures a plain Express baseline in the same run and fails the build if cold start goes past 2x that baseline. RSS has its own ceiling in the same file. So if a later change drags the start path back toward Vite, CI fails on a number instead of on someone's memory.&lt;/p&gt;

&lt;p&gt;We also keep a public benchmark repo that runs five SSR frameworks weekly in CI and stores the raw json for every cell: &lt;a href="https://github.com/Lomray-Software/ssr-benchmarks" rel="noopener noreferrer"&gt;https://github.com/Lomray-Software/ssr-benchmarks&lt;/a&gt;. Read it yourself, our claims here are about our own before and after, not about anyone else's framework.&lt;/p&gt;

&lt;p&gt;If you run SSR on Vite, go look at what your production process loads before it answers anything. Count the modules, or run start under --cpu-prof.&lt;/p&gt;

&lt;p&gt;We were loading a build tool in production. It cost about 100 ms and 40 MiB per process.&lt;/p&gt;

&lt;p&gt;Code: &lt;a href="https://github.com/Lomray-Software/vite-ssr-boost" rel="noopener noreferrer"&gt;https://github.com/Lomray-Software/vite-ssr-boost&lt;/a&gt;&lt;br&gt;
Docs: &lt;a href="https://lomray-software.github.io/vite-ssr-boost/" rel="noopener noreferrer"&gt;https://lomray-software.github.io/vite-ssr-boost/&lt;/a&gt;&lt;br&gt;
The PRs with the full detail: &lt;a href="https://github.com/Lomray-Software/vite-ssr-boost/pull/53" rel="noopener noreferrer"&gt;https://github.com/Lomray-Software/vite-ssr-boost/pull/53&lt;/a&gt;, &lt;a href="https://github.com/Lomray-Software/vite-ssr-boost/pull/54" rel="noopener noreferrer"&gt;https://github.com/Lomray-Software/vite-ssr-boost/pull/54&lt;/a&gt;, &lt;a href="https://github.com/Lomray-Software/vite-ssr-boost/pull/55" rel="noopener noreferrer"&gt;https://github.com/Lomray-Software/vite-ssr-boost/pull/55&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>performance</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
