|
| 1 | +# lux.js SPA improvements |
| 2 | + |
| 3 | +## Executive summary: Potential solutions |
| 4 | + |
| 5 | +Adding a "SPA mode" in the RUM Settings UI would be the easiest way to simplify SPA implementations. It requires the smallest change to lux.js and allows us to change implementation details on a per-customer level without making further lux.js changes. |
| 6 | + |
| 7 | +Adding more methods to the `LUX` API is a middle-ground kind of solution that gives implementers more control but does not significantly reduce complexity. |
| 8 | + |
| 9 | +We should aspire to completely automate SPA implementations, possibly with the help of Chromium's soft navigation work, but mostly aiming for a generic cross-browser solution. This would be a large undertaking and require a lot of testing and validation with customers. |
| 10 | + |
| 11 | +## The problem (high level) |
| 12 | + |
| 13 | +### Implementing lux.js in a SPA requires knowledge of various config items and methods |
| 14 | + |
| 15 | +Much of the internal lux.js architecture is designed around full page navigations. SPA support was an afterthought, and setting `LUX.auto = false` essentially puts lux.js into "full manual control" mode. |
| 16 | + |
| 17 | +A proper lux.js implementation in a SPA requires correctly use of `LUX.auto`, `LUX.sendBeaconOnPageHidden`, `LUX.init()`, `LUX.markLoadTime()`, and `LUX.send()`. Getting just one of these wrong can throw the whole implementation off. |
| 18 | + |
| 19 | +### Many implementations seem to be incorrect, resulting in lost data and mistrust in SpeedCurve's metrics |
| 20 | + |
| 21 | +The most common most of failure for lux.js in SPAs is `LUX.send()` being called too soon. Despite many documentation tweaks and hand holding, this still happens regularly. |
| 22 | + |
| 23 | +The other common issue is that SpeedCurve's "Page Load" metric is the same for full page navigations and soft navigations. Most implementations do not use `LUX.markLoadTime()`, so most soft navigation Page Load time is set when `LUX.send()` is called. |
| 24 | + |
| 25 | +### It is difficult to debug lux.js implementations from the outside |
| 26 | + |
| 27 | +The internal lux.js logs (from `LUX.getDebug()`) do not always expose enough information to determine whether an implementation is correct. Sometimes the only way to know is by reviewing the actual implementation. |
| 28 | + |
| 29 | +### We get one shot for a good implementation |
| 30 | + |
| 31 | +Once a lux.js implementation is deployed, it is difficult to get customers to change it. Most of the time we only get one shot. |
| 32 | + |
| 33 | +## Finding a solution |
| 34 | + |
| 35 | +### Current capabilities |
| 36 | + |
| 37 | +- All of the low level APIs required for a good SPA implementation already exist in lux.js. |
| 38 | +- rum-backend is capable of injecting most `LUX` config values based on SpeedCurve account settings. |
| 39 | + |
| 40 | +### Limitations |
| 41 | + |
| 42 | +- The lux.js API is append-only. We can add things (new methods, new method parameters) but we can never change things (swap method parameter order, rename methods). We can also never remove things. Anything we add must be well thought-out, or we will compromise our goal of having the smallest and fastest RUM SDK. |
| 43 | + |
| 44 | +## Solution #1: SPA mode |
| 45 | + |
| 46 | +### Overview |
| 47 | + |
| 48 | +- Add a "SPA mode" toggle in the SpeedCurve RUM Settings UI. |
| 49 | +- Add a new method to indicate the beginning of a soft navigation. |
| 50 | +- Remove the Page Load metric from soft navigations unless `LUX.markLoadTime()` is explicitly called. |
| 51 | + |
| 52 | +### Pros |
| 53 | + |
| 54 | +- Gives SpeedCurve full control over the implementation without needing to release lux.js updates. |
| 55 | +- No risk of misconfiguration, requires just one function call on soft navigation. |
| 56 | +- Gives SpeedCurve valuable data about how many RUM customers use SPA mode. |
| 57 | +- We can change what SPA mode does per-customer based on their other settings. |
| 58 | +- Only one new API method that has low risk of becoming redundant or deprecated. |
| 59 | +- Backwards compatible with existing implementations. |
| 60 | +- Remove confusion around the Page Load metric. |
| 61 | + |
| 62 | +### Cons |
| 63 | + |
| 64 | +- Requires a new API method. |
| 65 | +- Implementers are forced to choose one extreme or the other: full manual control (`LUX.auto = false`) or full autopilot. |
| 66 | +- Does not fix the fundamental issues with lux.js architecture. |
| 67 | + |
| 68 | +### Technical details |
| 69 | + |
| 70 | +#### SPA mode |
| 71 | + |
| 72 | +When "SPA mode" is enabled, we will automatically inject the appropriate `LUX` config into lux.js. This may be something like: |
| 73 | + |
| 74 | +```js |
| 75 | +// Don't send the beacon on onload |
| 76 | +LUX.auto = false; |
| 77 | + |
| 78 | +// Do send the beacon on pagehide |
| 79 | +LUX.sendBeaconOnPageHidden = true; |
| 80 | + |
| 81 | +// Do enable bfcache support |
| 82 | +LUX.newBeaconOnPageShow = true; |
| 83 | +``` |
| 84 | + |
| 85 | +#### Soft navigation API |
| 86 | + |
| 87 | +A new method would be called when the user initiates a soft navigation. This is the only "hard implementation" that lux.js implementers would be required to do. Under the hood, this method would essentially be: |
| 88 | + |
| 89 | +```js |
| 90 | +function beginSoftNavigation() { |
| 91 | + // Send the beacon for the current (previous?) navigation |
| 92 | + LUX.send(); |
| 93 | + |
| 94 | + // Initialise a new beacon for the upcoming soft navigation |
| 95 | + LUX.init(); |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +Taking inspiration from [the "V2" Usage Changes document](./usage-changes.md), this new method could also take a config object and apply it to the upcoming soft navigation: |
| 100 | + |
| 101 | +```js |
| 102 | +LUX.beginSoftNavigation({ |
| 103 | + label: "cart.checkout", |
| 104 | + data: { |
| 105 | + cart_size: 24, |
| 106 | + cart_value: 804.2, |
| 107 | + user_type: "guest", |
| 108 | + }, |
| 109 | +}); |
| 110 | +``` |
| 111 | + |
| 112 | +## Solution #2: More high-level APIs |
| 113 | + |
| 114 | +### Overview |
| 115 | + |
| 116 | +In contrast to solution #1, this solution would be done completely with "hard implementation", but add more to the `LUX` API to reduce implementation friction. For example: |
| 117 | + |
| 118 | +```js |
| 119 | +// During bootstrap / init |
| 120 | +LUX.enableSPAMode(); |
| 121 | + |
| 122 | +// When user initiates a soft navigation |
| 123 | +LUX.beginSoftNavigation(); |
| 124 | + |
| 125 | +// When loading is complete |
| 126 | +LUX.endSoftNavigation(); // alias for LUX.markLoadTime() |
| 127 | +``` |
| 128 | + |
| 129 | +This solution would also get rid of the Page Load metric for soft navigations (unless `LUX.markLoadTime()` is called). |
| 130 | + |
| 131 | +### Pros |
| 132 | + |
| 133 | +- Gives implementers control without forcing them to use "full manual mode" (`LUX.auto = false`). |
| 134 | +- We can change what SPA mode does in future lux.js releases. |
| 135 | +- Specific SPA-related APIs clarify implementer intent. |
| 136 | +- Remove confusion around the Page Load metric. |
| 137 | + |
| 138 | +### Cons |
| 139 | + |
| 140 | +- Does not allow for per-account implementation tweaks. |
| 141 | +- Adds at least 3 new methods to the `LUX` API, increasing risk of redundancy or deprecation. |
| 142 | +- Some risk of misconfiguration if `LUX.enableSPAMode()` is not called at the correct time. |
| 143 | + |
| 144 | +### Technical details |
| 145 | + |
| 146 | +The new APIs might be implemented as such: |
| 147 | + |
| 148 | +```js |
| 149 | +function enableSPAMode() { |
| 150 | + LUX.auto = false; |
| 151 | + LUX.sendBeaconOnPageHidden = true; |
| 152 | + LUX.newBeaconOnPageShow = true; |
| 153 | +} |
| 154 | + |
| 155 | +function beginSoftNavigation() { |
| 156 | + LUX.send(); |
| 157 | + LUX.init(); |
| 158 | +} |
| 159 | + |
| 160 | +function endSoftNavigation() { |
| 161 | + LUX.markLoadTime(); |
| 162 | +} |
| 163 | +``` |
0 commit comments