Field notes / 01 ·
A small site.
And the limits we found.
The foundation works. Looking closely at it made both its usefulness and its rough edges easier to see.

01 / What already works
There is a useful website here.
We set out to build this website from its actual requirements. At this revision, React Router and Vite produce public HTML pages, client-side navigation and separate route bundles. React handles the interactive parts. The production Node process serves the finished files; it does not render React on every request.
Type checking, linting and the production build passed during this review. The build contained HTML for the three public pages and a separate interaction-probe chunk. That is tangible progress. It is also a narrower statement than saying every browser and deployment scenario has been verified.
Our current judgment is that this foundation is already a practical fit for a small corporate website with no dynamic content, provided its images are prepared in advance. A company introduction, service pages and a handful of public stories do not inherently need a database or a request-time rendering service.
For that scope, the approach feels lightweight and easy to maintain. That is our assessment of the current structure, not a performance benchmark or a claim that every server edge case is solved.
This repository is the website itself, not a packaged engine. Its value as an example is that we can inspect what those modest requirements actually cost.
02 / The most visible weight
The pictures need preparation.
The homepage illustrations alone account for about 6.7 MB of PNG files in the build; the hero is roughly 1.27 MB. Those are artifact sizes, not a measured first-page transfer. Lower images are lazy-loaded, but that does not make an oversized hero smaller.
Image resizing and compression are the clearest improvements we see. Preparing appropriate dimensions and formats before publication is already enough for the small-site use case. An automated image pipeline would make that process more convenient, but we do not need to mistake convenience for a prerequisite.

03 / Where simplicity surprised us
A short server is still a server.
sirv was convenient at the start. It kept the file-serving wrapper small. A closer look exposed behavior we do not want to carry forward unchanged, and it has become our first candidate for replacement.
In isolated instances of the built server, two requests terminated the process: an invalid URL, GET //[, threw in our URL parsing; a reversed range, Range: bytes=10-1, threw inside sirv 3.0.2. The responsibility is split between our wrapper and its dependency. Either way, a bad request should not take the service down.
The production file index also assumes a stable directory. That is reasonable for a build artifact, but surprising for the mounted shared directory: new files are not discovered automatically, and replaced files can retain stale size and ETag metadata until restart. A directory that does not exist at startup is not attached later.
Even successful responses deserve scrutiny. A matching ETag produced a 304 response without ETag, Cache-Control or Vary headers because sirv returned before our header callback. And enabling its gzip and Brotli options only selects precompressed files; our build does not generate those files. The switches alone do not compress the response.
04 / Rules that belong to the site
Files do not all have the same lifetime.
Our current rule gives anything under an assets path a year of immutable caching. The same rule reaches shared files. That is too broad: a stable filename does not prove that its contents will never change.
A tile-serving use case might justify a long lifetime, although a month or a week may be sufficient. Other sections need different freshness rules. Browser caching and shared-cache lifetimes also need not be identical. These are decisions about the resource, not a single switch for the whole server.
We have also encountered cases where one requested filename must be resolved against several directories. The order of that search matters. So does the distinction between “not found” and “found but unreadable.” This points toward our own static-file middleware policy: where to look and how to cache the result. How much HTTP machinery should live beneath it remains a separate decision.
The production dependency list has a similar boundary problem. React, React DOM, React Router and Linaria are installed alongside sirv, although this file-serving process needs only sirv and its dependencies. Their browser code is already bundled. The Vite configuration also imports mrmime without declaring it directly. These are small signs that build-time and runtime responsibilities need a more deliberate separation.
05 / Two ways to run
The development path is a different path.
Development runs Vite rather than our production server. We have seen a failure after renaming files, but have not isolated its cause; attributing that incident to sirv would be wrong when sirv is not running. Vite or the React Router integration may be involved. That remains an observation to investigate, not a diagnosis.
Running development through Varnish can be intentional when testing caching. Direct access on the development port serves a different purpose. During this review, production HTTP tests were aimed at the cached development endpoint and failed. That tells us the target was unsuitable for those assertions; it does not establish a production-server regression.
An always-running application server is convenient when APIs and file policies need to handle requests in development too. At the same time, we want the possibility of publishing finished static files without our Node process. A shared server entry point and an independent static output seem compatible, but the integration has not been proven here. No hosting-specific deployment is being claimed.
Other choices are still provisional. Linaria is connected, but its styled component is unused and cross-file selectors, dynamic values, hydration, HMR and lazy CSS delivery have not received focused verification. Automated browser coverage and a documented cache-refresh procedure for publication are also missing. The README’s broad production-readiness language runs ahead of that evidence.
The conclusion, for this revision
Useful now. Worth revisiting.
We see a successful foundation for a modest content website, with a manageable structure and no need to add a permanent rendering service. Preparing the images properly would make that use case more convincing immediately.
We also see concrete server failures, overly broad cache rules and unresolved boundaries between development, build and delivery. Those observations qualify the result; they do not erase it. The architecture can be a good fit while its current file-serving implementation needs hardening or replacement.
We are keeping this conclusion attached to commit ccf201e. If the server, the middleware or even our opinion changes, it will be interesting to return here and see which assumptions survived.