Context
The site measures itself (ADR-0016, ADR-0017), but a visitor only meets that work as a page that describes it. The most direct demonstration is the visitor's own request: how long it took, where the time went, and what the measurement kept of it. That must not collect anything new, add an endpoint to a single host, or hand an attacker information that helps them tune requests.
Options
- A per-request lookup endpoint ("find my record"). Exact, but it needs a request identifier in the ledger, which makes records linkable and breaks the property ADR-0016 publishes, and it is a new target on a single host.
- Fetch timing from the API after load. No new data, but another request per page view, and the page would depend on the API (ADR-0003).
- Reflect nginx's timings in a
Server-Timingheader on the response the visitor already receives, and let the page combine it with the browser's own Navigation Timing.
Decision
The third option. nginx adds Server-Timing: proxy;dur=…, app;dur=…, t;desc="…" to page responses:
its own time on the request when the headers left, the part spent behind it (firewall and Next.js),
and when. The home page reads the navigation entry after hydration and shows the visitor's request
as a waterfall (DNS, connection and TLS, network and CDN, this server, download), the fields of the
ledger record nginx wrote for it, and the commit the page was built from.
What it never shows: the client address in any form, the WAF anomaly score or matched rules, the
remaining rate-limit allowance, upstream addresses or host names. Those would be a scoring oracle
or abuse calibration, not a demonstration. The record's server-only values (t, rt, b) are
shown as unknown rather than estimated.
A response that was stored and replayed (by a CDN or the browser cache) would carry another
request's timing. The page compares the header's timestamp with the time the browser sent its
request and, if the timing is older than two minutes, says the response was replayed instead of
presenting it as the visitor's. Pages are served with cf-cache-status: DYNAMIC today, so this is
a safeguard, not the normal path.
Consequences
- No new data is collected, stored or transmitted beyond values the visitor's own response carries.
- Origin processing time becomes visible to anyone, rounded to the millisecond. It is coarse, the same for every visitor, and the published reports already contain its distribution.
- The component renders nothing until it has real values, and nothing after a client-side navigation, when the browser's entry describes a different document.
- The user-agent classification shown is nginx's own list, copied into the page; a unit test reads nginx.conf and fails when they differ.