Skip to content

Instantly share code, notes, and snippets.

@csswizardry
Last active August 3, 2026 10:10
Show Gist options
  • Select an option

  • Save csswizardry/6dd60f7536835d9c42ff05efd1b75a98 to your computer and use it in GitHub Desktop.

Select an option

Save csswizardry/6dd60f7536835d9c42ff05efd1b75a98 to your computer and use it in GitHub Desktop.
LUX Sidecar – Your SpeedCurve Companion
_ingest/
_assets/
llm-scripts/

LUX Sidecar – Your SpeedCurve Companion

mascot

N.B. LUX Sidecar requires a SpeedCurve RUM account. Sign up for a free trial.

LUX Sidecar adds a small set of browser-derived dimensions to SpeedCurve LUX. It is a companion script, not a replacement for the LUX agent: it sends its values through LUX.addData() and relies on LUX to collect and beacon them.

The script is deliberately zero-config. Include it on pages where the standard LUX snippet has already established window.LUX.addData(), and it will record the values that the browser makes available for that navigation. Where LUX is not present, it remains inert.

Installation

Load your usual SpeedCurve snippet first, then load LUX Sidecar. The external LUX agent may load before or after Sidecar; the inline snippet must come first so that LUX.addData() is available.

<!-- Your standard SpeedCurve LUX snippet goes here. -->
<script>[]</script>

<script
  src=https://cdn.speedcurve.com/js/lux.js?id=YOUR_ID
  async
  crossorigin=anonymous
></script>
<script src=path/to/lux-sidecar.js defer></script>

The supplied file is a classic browser script; no module loader, build step, or initialisation call is required.

Data Added

LUX Sidecar adds the following custom data when its underlying browser APIs and navigation values are available:

Key Value Meaning
rtt Number (ms) The Network Information API’s current navigator.connection.rtt estimate.
fromCache Boolean true when the navigation’s transferSize is zero; false when it is greater than zero. No value is added for other values.
frombfCache Boolean Whether the pageshow event reports that the view was restored from the back/forward cache.
fromPrerender Boolean Whether the document is currently prerendering, or its navigation has a non-zero activationStart.
uno Number (ms) Unattributed Navigation Overhead: the part of time to first byte not covered by the named redirect, DNS, connection, or request-to-response-start phases.
ttlb Number (ms) Time from navigation start to responseEnd: the complete document-response time, beyond first byte.

uno and ttlb are rounded to the nearest millisecond and added only when the result is finite and non-negative.

Interpreting UNO

Tim Vereecke coined the term Unattributed Navigation Overhead to describe TTFB sub-parts that may or cannot be directly attributed due to privacy constraints. Oftentimes, though not always, this is cross-origin redirects. Thus, UNO itself is a residual and derived measurement which represents everything observed but not directly attributable. For each Navigation Timing entry, Sidecar calculates it as:

(responseStart − startTime)
− (redirectEnd − redirectStart)
− (domainLookupEnd − domainLookupStart)
− (connectEnd − connectStart)
− (responseStart − requestStart)

Unavailable or protected phase timestamps contribute zero to their phase, so their elapsed time remains in the residual. This makes UNO useful for exposing time that the browser includes in initial-document TTFB but cannot attribute to the named phases it exposes. Calculate it per navigation before aggregating; subtracting separately calculated percentiles would not represent the same thing.

Browser Support And Caveats

The script uses Navigation Timing and, when exposed by the browser, the Network Information API and prerendering state. Missing data is expected: for example, rtt is not added if navigator.connection.rtt is unavailable, and no navigation-derived values are added where there is no Navigation Timing entry.

fromCache is a practical transfer-size heuristic, not a comprehensive cache taxonomy. It distinguishes zero-byte transfers from transfers that required network bytes; it does not attempt to classify every cache or revalidation outcome.

Prerendered views can include work that happened before the user activated the page. Use fromPrerender to segment those records when interpreting navigation timings such as UNO and TTLB.

Licence

LUX Sidecar is released under the MIT License. The licence text is retained in lux-sidecar.js.

/*! © Harry Roberts, csswizardry.com — released under the MIT License. */
/**
* MIT License
*
* Copyright (c) Harry Roberts
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Additional browser-derived data for SpeedCurve RUM.
*/
(() => {
const lux = window.LUX;
// Remain inert on pages where SpeedCurve has not been installed.
if (!lux || typeof lux.addData !== 'function') return;
const { connection } = navigator;
// Surface the browser’s current estimate of network round-trip latency.
if (connection && 'rtt' in connection) {
lux.addData('rtt', connection.rtt);
}
const navigation = performance.getEntriesByType('navigation')[0];
if (!navigation) return;
// Separate locally served documents from those that required network
// transfer.
const { transferSize } = navigation;
if (transferSize === 0) {
lux.addData('fromCache', true);
} else if (transferSize > 0) {
lux.addData('fromCache', false);
}
// Keep restored views distinct from conventional navigations in RUM analysis.
window.addEventListener('pageshow', (event) => {
lux.addData('frombfCache', event.persisted);
});
// Preserve prerender history so pre-activation timings remain interpretable.
lux.addData(
'fromPrerender',
document.prerendering || navigation.activationStart > 0
);
// Unattributed Navigation Overhead (UNO): TTFB not covered by named phases.
// https://calendar.perfplanet.com/2024/uno/
const span = (end, start) => Math.max(0, end - start);
const uno = Math.round(
(navigation.responseStart - navigation.startTime) -
span(navigation.redirectEnd, navigation.redirectStart) -
span(navigation.domainLookupEnd, navigation.domainLookupStart) -
span(navigation.connectEnd, navigation.connectStart) -
span(navigation.responseStart, navigation.requestStart)
);
if (Number.isFinite(uno) && uno >= 0) {
lux.addData('uno', uno);
}
// Capture the full document response time, beyond the first byte as captured
// by TTFB.
if (navigation.responseEnd && navigation.startTime >= 0) {
const ttlb = Math.round(navigation.responseEnd - navigation.startTime);
if (Number.isFinite(ttlb) && ttlb >= 0) {
lux.addData('ttlb', ttlb);
}
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment