Real User Monitoring: LUX

LUX Overview

SpeedCurve's RUM product is called LUX. The name is a play on "Live User eXperience" and reflects how we've taken a different approach compared to other Real User Monitoring products. SpeedCurve's mission is to help designers and developers create joyous, fast user experiences. To do that, we focus on metrics that do a better job of revealing what the user's experience is really like.

In the world of performance, there are two types of metrics: synthetic and RUM.

  • Synthetic is from traffic that's produced synthetically, without any real users. This is what SpeedCurve has done from the beginning where customers schedule specific URLs to be tested at regular intervals. The pros of synthetic testing include: a controlled environment which makes it easier to detect regressions, a way to test new sites that don't have any real users yet, and the ability to instrument the test agent for deeper metrics such as memory, CPU and screenshots.
  • RUM, as the name implies, is from real user traffic. The advantage of RUM is that it is your actual real users, so there's no need to simulate variables like connection speed, CPU, geographic location, popular URLs, and user account settings.

SpeedCurve has both types of metrics enabling designers and developers to get the full spectrum of metrics in one place.

In addition to standard RUM metrics like page load and DNS lookup times, LUX includes innovative new metrics that have more to do with the user experience like start render time, number of critical blocking resources, images above the fold, and viewport size. LUX's RUM metrics help you figure out which design and development improvements will make your users happier and your business more successful.

Since SpeedCurve has both synthetic monitoring and RUM, we can mash those up to give better insights. For example, we compare your synthetic metrics to RUM so you understand how your synthetic performance will translate when staged code is pushed to production. Also, we give you feedback on which URLs, browsers, and geo locations are popular from RUM to help you improve your synthetic test settings to better approximate the real user experience.

You can see SpeedCurve RUM in action by viewing the LUX demo account. The data comes from where Pat Meenan has agreed to inject the LUX snippet. There are four LUX dashboards: Live, Users, Performance, and Design.

LUX Live Dashboard VIEW DEMO

The LUX Live dashboard constantly updates to show the users currently visiting your site.

LUX Live

You can hover over a row to highlight all visits for that user's session. Clicking on a row shows the details for that user's visit including Navigation Timing, User Timing, user interaction, and page design metrics.

LUX Page Details

LUX Users Dashboard VIEW DEMO

The LUX Users dashboard shows information about your users including page views broken out by type of user interaction. It also shows the top pages, browsers, viewports, cities, and countries across all your users.

Page Views

LUX Performance Dashboard VIEW DEMO

LUX Performance shows the core performance metrics like backend vs frontend time (where everyone should start), User Timing custom metrics (if you have any), and time to first user interaction (often the best reflection of the user's experience). It also contains a great example of mashing up RUM and synthetic for start render and page load time.

LUX and Synthetic

LUX Design Dashboard VIEW DEMO

The LUX Design dashboard has most of our innovative views of how performance and design impact the user experience. If your start render time is slow, it's good to see how many critical blocking stylesheets and scripts are being served to real users, especially if you have third party tags. Comparing the number of images above-the-fold with the total number of images indicates whether you should be lazy loading images outside of the initial viewport. Similarly, comparing viewport size to document size indicates if you have other content that you should lazy load. LUX also shows the most popular DOM elements that users interact with.

Critical Blocking Resources


We also make it easy to slice-and-dice the data. Expanding the Dropdown reveals various dimensions that can be used to segment the data. You can choose from the top values that are shown, or enter a value. Wildcards are supported. You can also click on any of the lists of values in the dashboards to filter on that value.

The example below finds all the pages from Chrome 54 that contain "result" in the URL where the user's first interaction ("IX") was with the DOM element "grades".

LUX Dropdown

Getting Started

To get started with LUX, complete the LUX enquiry form. Make sure to include which domain(s) you want monitored and how many page views per month you want to pay for. We'll issue a quote based on your information and when we receive a purchase order we'll enable LUX for your account. At that point, the "LUX" menu is shown next to the other dashboards in SpeedCurve. Or if you want a free trial, just specify as such and we'l let you try it 30 days for free.

Once we have responded to your enquiry, you can insert the lux.js script into your website using one of the two snippets shown below. This script is loaded asynchronously so it won't harm the performance of your site. Insert the lux.js script into your pages like this:

LUX = window.LUX || {};
LUX.samplerate = SAMPLE_RATE;
<script src="" async defer></script>

In the snippet above, replace LUX_ID with the team's LUX ID found in your Settings under Admin | Teams

Set the sample rate to a value that matches the LUX plan you want to sign up for. For example, if your site typically gets 900 million page views per month, and you want one LUX plan that covers a maximum of 10 million page views per month, then set the sample rate to LUX.samplerate=1; (which is 1%).

It's best to put the LUX snippet as early in the page as possible, i.e., at the top of the HEAD element.

At SpeedCurve, we believe strongly that teams should add custom metrics to their pages with the User Timing spec. Unfortunately, some browsers don’t support the User Timing spec (most importantly mobile Safari on iOS). To gather custom metrics on all browsers you can use the LUX.mark() and LUX.measure() functions. If you’d like to do this, you must use this longer snippet:

LUX=(function(){var a=("undefined"!==typeof(LUX)&&"undefined"!==typeof(LUX.gaMarks)?LUX.gaMarks:[]);var d=("undefined"!==typeof(LUX)&&"undefined"!==typeof(LUX.gaMeasures)?LUX.gaMeasures:[]);var h="LUX_start";var j=window.performance;var k=("undefined"!==typeof(LUX)&&LUX.ns?LUX.ns:( Date())));if(j&&j.timing&&j.timing.navigationStart){k=j.timing.navigationStart}function e(){if(j){if({return}else{if(j.webkitNow){return}else{if(j.msNow){return}else{if(j.mozNow){return}}}}}var Date());return m-k}function b(m){if(j){if(j.mark){return j.mark(m)}else{if(j.webkitMark){return j.webkitMark(m)}}}a.push({name:m,entryType:"mark",startTime:e(),duration:0});return}function l(o,s,m){if("undefined"===typeof(s)&&g(h)){s=h}if(j){if(j.measure){if(s){if(m){return j.measure(o,s,m)}else{return j.measure(o,s)}}else{return j.measure(o)}}else{if(j.webkitMeasure){return j.webkitMeasure(o,s,m)}}}var q=0,n=e();if(s){var r=g(s);if(r){q=r.startTime}else{if(j&&j.timing&&j.timing[s]){q=j.timing[s]-j.timing.navigationStart}else{return}}}if(m){var p=g(m);if(p){n=p.startTime}else{if(j&&j.timing&&j.timing[m]){n=j.timing[m]-j.timing.navigationStart}else{return}}}d.push({name:o,entryType:"measure",startTime:q,duration:(n-q)});return}function g(m){return c(m,f())}function c(p,o){for(i=o.length-1;i>=0;i--){var n=o[i];if({return n}}return undefined}function f(){if(j){if(j.getEntriesByType){return j.getEntriesByType("mark")}else{if(j.webkitGetEntriesByType){return j.webkitGetEntriesByType("mark")}}}return a}return{mark:b,measure:l,gaMarks:a,gaMeasures:d}})();LUX.ns=( Date()));[];LUX.cmd=function(a){};LUX.init=function(){LUX.cmd(["init"])};LUX.send=function(){LUX.cmd(["send"])};LUX.addData=function(a,b){LUX.cmd(["addData",a,b])};
LUX.samplerate = SAMPLE_RATE;
<script src="" async defer></script>

This longer snippet defines LUX.mark() and LUX.measure(), so you can use these functions immediately even while the lux.js script is being loading asynchronously.


What does LUX cost?

Please take a look at our pricing page. Note that you can buy multiple bundles to match your sites's traffic. Fill out the LUX enquiry form to find out about discounts for higher volumes.

Is there an API?

Yes, see the LUX API section.

Is there a way to segment the data and do A/B testing?

You can add segmentation info by using LUX.addData(name, value) where "name" is a string that is the name of the variable and "value" is a string, int, or boolean. Since lux.js is loaded asynchronously, you have to make sure LUX.addData exists before you call it. You can do this by calling it in the lux.js onload handler. If you do this, you'll see "Customer data" filters in the LUX Dropdown which allow you to choose the segments you want to view. Check out our A/B Testing blog post for an example case study.

Do search engine bots show up in the data?

We exclude search engine bot data from the LUX dashboards. We do this because often the search engine crawlers disable timing information. Since there’s no timing information and no user interaction, the search engine data can skew the LUX dashboards to NOT reflect real user experience.


LUX is a JavaScript library. LUX works just by loading the lux.js script. Nothing else is needed. But there is a LUX API that provides additional features as described here. All of the API is accessed through the LUX global variable.

LUX Properties

You can set these properties to alter the default behavior of LUX. Since lux.js is loaded asynchronously, use the following pattern to make sure LUX is defined before setting a property:

LUX = window.LUX || {};

If false, the LUX beacon is not sent at the window load event. The default value is true. The reason for setting this to false is if your page is a template and the actual content is loaded after the window load event, or something else happens after the onload that you want included in the beacon. If you set to false, then you need to call LUX.send() in order for the beacon to be sent.


LUX = window.LUX || {}; = false;

This is the “page label” used to group LUX data by page type. The default value is window.title. It’s a best practice to set pages to the same label used in your SpeedCurve synthetic Settings for the same page type. Maximum length is 255 characters.


LUX = window.LUX || {};
LUX.label = 'home';

This is the sample rate for determining whether the LUX beacon is sent. You can set it to an integer from 0 to 100 inclusive as the percentage of metrics to accept. This is useful for collecting data while staying under your LUX budget of page views per month. The percentage is based on the session ID, therefore, entire sessions are accepted or rejected in whole.


LUX = window.LUX || {};
LUX.samplerate = 10; // 10% of metrics are accepted

LUX Functions

Since lux.js is loaded asynchronously, you need to make sure these functions are defined before using them. An easy way to do this is to add an onload handler to the lux.js SCRIPT tag, and call the LUX functions inside that handler.

Because LUX.mark and LUX.measure may need to be called before lux.js has loaded, these two functions are defined in the longer snippet. (See the LUX Snippet section.) In other words, if you use LUX.mark or LUX.measure then you should use the longer snippet.

This function is used to add your own custom data to the beacon. This "customer data" can be used in the SpeedCurve UI to segment the LUX performance data. This is useful for doing A/B testing. You can also store business metrics (e.g., cart size or conversions) and plot those along with performance metrics to see correlations.


LUX.addData('cartsize', 128);
LUX.addData('experiment A', 'control');

LUX works automatically for normal ("Web 1.0") pages. But if your site is a Single Page App (SPA) then you need to add code to initialize LUX at the beginning of each SPA "page view" and send the LUX beacon at the end. Call this function at the beginning of the SPA page transition.


// start SPA page transition

This function is identical to performance.mark from the User Timing spec for marking a specific time milestone in the page. It's provided as a shim for browsers that don't support the User Timing spec, most notably Safari. Maximum length for markName is 128 characters.


LUX.mark("before JSON");

This function is identical to performance.measure from the User Timing spec for measuring the delta between two time milestones in the page. It's provided as a shim for browsers that don't support the User Timing spec, most notably Safari. Maximum length for name is 128 characters.


LUX.measure("JSON request", "before JSON");

This function is used along with LUX.init for SPA sites.


// end of SPA page transition

LUX HTML Attributes

When the first interaction with the page is a click or key press, LUX tracks which DOM element was interacted with. The name that is recorded is based on searching the DOM element and its ancestors for the data-sctrack attribute. If data-sctrack is not found, then the first DOM element ID is used.

Example: If the user clicked on any of these links, then "navbar" would be tracked as the DOM element the user interacted with.

<div data-sctrack="navbar">
    <li> <a href="/" id=home>Home</a>
    <li> <a href="/shop" id=shop>Shop</a>
    <li> <a href="/search" id=search>Search</a>