How to create the best experience for every user with the newest web APIs
We are building the web on premium devices, fast internet connections and data flat rates. That means that the consequent pages built work well for us but don’t work as well for most of our users. This will become an even bigger problem for the next billion users that can only afford cheap devices and rely mainly on cellular connections.
To provide a good experience for every user we have to make smart decisions based on:
- user preferences
- the current network connection
- the device capabilities
Until now this has been very hard to do. However, thanks to exiting new web APIs this could change in the near future. In this article I will explore these new APIs and share my best practices with you.
User preferences — respect the user
Wouldn’t it be great to get hints from the user what she or he prefers? With the new HTTP Client Hints this is finally possible.
In Google Chrome and Opera the users can already set the save-data option. This is a great way for users to say: please don’t waste my data.
There are good reasons for users to save data:
- Expensive pre/post-paid data plans, that are the standard in many countries. For more information, check out: What does my site costs
- A data flat rate which gets throttled after reaching a limit
- A very slow internet connection
The user hints are added as a http request header and are also available via a Javascript API (navigator.connection.saveData).
Save-data header
Most of the projects I work on use aggressive caching strategies. Using the save-data header allows you to deliver a pre-rendered save-data version of your page, which is my preferred approach:
// false by default.
$saveData = false;
// Check if the `Save-Data` header exists and is set to a value of "on".
if (isset($_SERVER["HTTP_SAVE_DATA"]) && strtolower($_SERVER["HTTP_SAVE_DATA"]) === "on") {
// `Save-Data` detected!
$saveData = true;
}
Save-data Javascript API
You can also use the Javascript API to make decisions on the client if necessary:
if ("connection" in navigator) {
if (navigator.connection.saveData === true) {
// Implement data saving operations here.
}
}
Best practices for save-data:
- ‘Lazy load’ resources which are outside of the viewport. Make use of the new intersectionObserver to do so
- Don’t load web fonts
- Don’t load images which are not necessary to understanding the content
- Use very high compressed images
- Don’t load high resolution (retina) images
- Use progressive disclosure: render less content with the possibility to load more if the user actively asks for it
- Load text-only ads
- Be very careful with pre-fetching data using service workers
- Make the user aware they are seeing a “save-data” version of the page. Offer a button to switch to the full experience
In case of a conflict between best practices (for example: avoid lazy loading on a cellular connection), I would always prioritise approaches that save data.
Respect the user: always make user preferences the highest priority.
Browser support
In June 2018 this is supported by Google Chrome (from version 46) and Opera (from version 33). Check out the current browser support.
More resources
- Check out the Google developer fundamentals on save-data for more ideas and code examples.
- Check out the HTTP Client Hints Draft to learn about other supported client hints.
Network — load quickly
The network type, latency, and bandwidth all have a great influence on the user experience. Unfortunately it can be hard to detect the network type and the current latency and bandwidth.
However, with the new Network Information API this might change soon.
The Network Type
To be able to make smart decisions it is important to know the current network type (ethernet, wifi, and cellular).
Ilya Grigorik did a great job explaining the differences between the network types in his book “High Performing Networking”, which I highly recommend.
I want to focus here on the cellular connections as they are the most challenging ones when it comes to latency and bandwidth.
Source: Ilya Grigorik, High Performance Browser Networking
Cellular connections are managed by the Radio Resource Controller which is part of the mobile network. To be able to serve as many users as possible it tries to minimize the connection times for each user. This is done by sending the devices into idle mode as soon as possible. As the mobile device radios are the second biggest power consumer, this also helps to save battery power which is a good thing.
With the Network Information API we can check for the connection type.
// Get the connection type.
var type = navigator.connection.type;
// Result: 'bluetooth', 'cellular', 'ethernet', 'none', 'wifi', 'wimax', 'other', 'unknown'
Effective Type
The network type alone doesn’t tell you much about the network latency and bandwidth. If you have ever used the wifi in a hotel or train, you’ll know what I am talking about.
The effective type tells you how fast the current connection is. It uses the values ‘slow-2g’, ‘2g’, ‘3g’, or ‘4g’ to describe the current speed. If the result is ‘slow-2g’ it doesn’t mean the network type is 2g, but that the network behaves like a slow 2g network, taking the latency and bandwidth into account.
// Get the connection type.
var type = navigator.connection.effectiveType;
// Result: 'slow-2g', '2g', '3g', or '4g'
Observing changes
Network connections are prone to change. To be able to adapt you’ll need to be aware of changes. This is where the onchange property comes in handy.
// Register for event changes.
navigator.connection.onchange = changeHandler;
Best practices
If the connection type is cellular I propose to follow these best practices:
1. Load as much as possible and go idle (warning: don’t use any of these best practices if the save-data header is set):
- Avoid lazy loading techniques. On 3G networks it can take up to 3.5 seconds to initialize a new connection which would make the user wait too long. On top of this we would waste battery power by constantly initializing new connections.
- Use service workers to preload content which most likely will be used soon.
- Predict what the user is doing next and preload the page in the background. This can be the next step page of a multiple step process. Alternatively, you can predict based on the mouse movements on which link the user will click on imminently. Check out Futurelink.
- Collect user data and send it via the Beacon API:
- Collect user data and send it to the server using the Beacon API which is well supported. This prevents the client from constantly making new connections to the server which can drain the battery. Make sure your 3rd party tools follow this best practice as well.
If the current network is very slow (slow-2g or 2g) you might want to consider following the best practices we already discussed in the save-data section.
Browser support
At the time of writing the Network Information API is still experimental. Check out the current browser support.
Test your websites and apps constantly on slow connections.
Device — run smoothly
When it comes to devices, we have to consider two trends:
- Premium devices are becoming more and more powerful with each iteration (~$1,000).
- Cheap devices with a stable feature set are getting cheaper with each iteration (~$30). The next billion internet users will join because they finally can afford a device.
The gap between premium and cheap devices is becoming bigger with each iteration. his might become the next big challenge for web developers.
This graphic compares the same page rendered on different devices. The Scripting and Rendering takes considerably longer on less powerful devices.
How to detect the device capability?
The “Cut the mustard” method is often used to decide whether to load a simple or full experience of a website. This is done by simply checking if the browser supports features which are only supported by modern browsers.
However, as modern browsers can also run on old devices the “Cut the mustard” method is far from being perfect.
A better approach could be to check the Device Memory and hardwareConcurrency:
// Get the connection type.
var type = navigator.hardwareConcurrency;
The hardwareConcurrency is the number of logical processors potentially available to the user agent.
// Get the connection type.
var type = navigator.deviceMemory;
The deviceMemory returns how much RAM the device has in gigabytes, rounded down to the nearest power of two. The API also features a Client Hints Header.
Battery status
The battery status of an device can also influence the user experience. To save battery, the save-data best practices can be applied. Additionally I would avoid all animations.
The battery status can be retrieved via the Battery Status API. Warning: the Battery Status API is not longer supported by webkit and Firefox due to privacy reasons.
Best practices for slow devices
- Parsing and executing Javascript takes a lot longer on old devices. Only load and run Javascript for what is currently needed on the page.
- Be careful with animations. They often get janky on old devices and it might be a better experience to avoid showing them.
- It can become a challenge for low memory devices to handle big images (for example image sprites) and web fonts.
- Get a cheap Android device (below $200) to test your app or website. The second best thing you can do is throttle the CPU with Chrome Dev Tools.
There are other great APIs which help you to control when and where your Javascript code is running. This is beneficial for all kinds of devices. If these concepts are new to you I would recommend to read the linked articles from Paul Lewis:
- requestAnimationFrameRun Javascript at the beginning of the frame and avoid repainting.
- requestIdleCallback
This will make your non-critical Javascript run only during idle time, which is great as it no longer blocks user requests.
Browser Support
- hardwareConcurrency
- Battery Status API
- requestAnimationFrame
- requestIdleCallback
- deviceMemory (no data available at caniuse.com)
Test your websites and apps constantly on old and cheap devices.
New: Xperience Cards Workshop
At Netcentric we offer a brand new one day workshop which covers all these topics and more. The workshop helps the project team to deliver the best user experience for every user. Stay tuned for more information on this.
Sources
- Building for Billions, Tal Oppenheimer, 06/2016
- Ilya Grigorik, High Performance Browser Networking
- Google Developer Fundamentals: Save-data
- W3C: HTTP Client Hints Draft
- W3C: Beacon API
- W3C: Network Information API
- W3C: Battery Status API
- Using requestIdleCallback
- Optimize Javascript Execution
- Web Performance Resources, Fabian Krumbholz