The Save-Data client hint request header available in Chrome, Opera, and Yandex browsers lets developers deliver lighter, faster applications to users who opt-in to data saving mode in their browser.
Everyone agrees that faster and lighter web pages provide a more satisfying user experience, allow better content comprehension and retention, and deliver increased conversions and revenue. Google research has shown that "…optimized pages load four times faster than the original page and use 80% fewer bytes. Because these pages load so much faster, we also saw a 50% increase in traffic to these pages."
And, although the number of 2G connections is finally on the decline, 2G was still the dominant network technology in 2015. The penetration and availability of 3G and 4G networks is growing rapidly, but the associated ownership costs and network constraints are still a significant factor for hundreds of millions of users.
These are strong arguments for page optimization.
There are alternative methods for improving site speed without direct developer involvement, such as proxy browsers and transcoding services. Although such services are quite popular, they come with substantial drawbacks — simple (and sometimes unacceptable) image and text compression, inability to process secure (HTTPS) pages, only optimizing pages visited via a search result, and more. The very popularity of these services is itself an indicator that web developers are not properly addressing the high user demand for fast and light applications and pages. But reaching that goal is a complex and sometimes difficult path.
One fairly straightforward technique is to let the browser help, using the Save-Data request header. By identifying this header, a web page can customize and deliver an optimized user experience to cost- and performance-constrained users.
Supported browsers (below) allow the user to enable a *data saving- mode that gives the browser permission to apply a set of optimizations to reduce the amount of data required to render the page. When this feature is exposed, or advertised, the browser may request lower resolution images, defer loading of some resources, or route requests through a service that applies other content-specific optimizations such as image and text resource compression.
To determine when to deliver the "light" experience to your users, your application can check for the Save-Data client hint request header. This request header indicates the client's preference for reduced data usage due to high transfer costs, slow connection speeds, or other reasons.
When the user enables the data saving mode in their browser, the browser appends the Save-Data request header to all outgoing requests (both HTTP and HTTPS). As of this writing, the browser only advertises one *on- token in the header (Save-Data: on), but this may be extended in the future to indicate other user preferences.
Additionally, it's possible to detect if Save-Data is turned on in JavaScript:
Checking for the presence of the connection object within the navigator object is vital, as it represents the Network Information API, which is only implemented in Chrome, Chrome for Android, and Samsung Internet browsers. From there, you only need to check if navigator.connection.saveData is equal to true, and you can implement any data saving operations in that condition.Enabling the Data Saver extension in Chrome desktop.
If your application uses a service worker, it can inspect the request headers and apply relevant logic to optimize the experience. Alternatively, the server can look for the advertised preferences in the Save-Data request header and return an alternate response — different markup, smaller images and video, and so on.
When using Save-Data, provide some UI devices that support it and allow users to easily toggle between experiences. For example:
Notify users that Save-Data is supported and encourage them to use it.
Allow users to identify and choose the mode with appropriate prompts and intuitive on/off buttons or checkboxes.
When data saving mode is selected, announce and provide an easy and obvious way to disable it and revert back to the full experience if desired.
Remember that lightweight applications are not lesser applications. They don't omit important functionality or data, they're just more cognizant of the involved costs and the user experience. For example:
A photo gallery application may deliver lower resolution previews, or use a less code-heavy carousel mechanism.
A search application may return fewer results at a time, limit the number of media-heavy results, or reduce the number of dependencies required to render the page.
A news-oriented site may show fewer stories, omit less popular categories, or provide smaller media previews.
Provide server logic to check for the Save-Data request header and consider providing an alternate, lighter page response when it is enabled — e.g., reduce the number of required resources and dependencies, apply more aggressive resource compression, etc.
If you're serving an alternate response based on the Save-Data header, remember to add it to the Vary list — Vary: Save-Data — to tell upstream caches that they should cache and serve this version only if the Save-Data request header is present. For more details, see the best practices for interaction with caches.
If you use a service worker, your application can detect when the data saving option is enabled by checking for the presence of the Save-Data request header, or by checking the value of the navigator.connection.saveData property. If enabled, consider whether you can rewrite the request to fetch fewer bytes, or use an already fetched response.
Consider augmenting Save-Data with other signals, such as information about the user's connection type and technology (see NetInfo API). For example, you might want to serve the lightweight experience to any user on a 2G connection even if Save-Data is not enabled. Conversely, just because the user is on a "fast" 4G connection doesn't mean they aren't interested in saving data — for example, when roaming. Additionally, you could augment the presence of Save-Data with the Device-Memory client hint to further adapt to users on devices with limited memory. User device memory is also advertised in the navigator.deviceMemory client hint.
What you can achieve via Save-Data is limited only to what you can come up with. To give you an idea of what's possible, let's run through a couple of use cases. You may come up with other use cases of your own as you read this, so feel free to experiment and see what's possible!
While the Save-Data state is something you can detect in JavaScript via the navigator.connection.saveData property, detecting it on the server side is sometimes preferable. JavaScript can fail to execute in some cases. Plus, server side detection is the only way to modify markup before it's sent to the client, which is involved in some of Save-Datas most beneficial use cases.
The specific syntax for detecting the Save-Data header in server side code depends on the language used, but the basic idea should be the same for any application back end. In PHP, for example, request headers are stored in the $_SERVER superglobal array at indexes starting with HTTP_. This means you can detect the Save-Data header by checking the existence and value of the $_SERVER["HTTP_SAVE_DATA"] variable like so:
// 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; }
If you place this check before any markup is sent to the client, the $saveData variable will contain the Save-Data state, and will be available anywhere for use on the page. With this mechanism illustrated, let's look a few examples of how we can use it to limit how much data we send to the user.
Serve low resolution images for high resolution screens #
A common use case for images on the web involves serving images in sets of two: One image for "standard" screens (1x), and another image that's twice as large (2x) for high resolution screens (e.g., Retina Display). This class of high resolution screens is not necessarily limited to high end devices, and is becoming increasingly common. In cases where a lighter application experience is preferred, it might be prudent to send lower resolution (1x) images to these screens, rather than larger (2x) variants. To achieve this when the Save-Data header is present, we simply modify the markup we send to the client:
if ($saveData === true) { // Send a low-resolution version of the image for clients specifying `Save-Data`. ?><imgsrc="butterfly-1x.jpg"alt="A butterfly perched on a flower."><?php } else{ // Send the usual assets for everyone else. ?><imgsrc="butterfly-1x.jpg"srcset="butterfly-2x.jpg 2x, butterfly-1x.jpg 1x"alt="A butterfly perched on a flower."><?php }
This use case is a perfect example of how little effort it takes to accommodate
someone who is specifically asking you to send them less data. If you don't like
modifying markup on the back end, you could also achieve the same result by
using a URL rewrite module such as Apache's
mod_rewrite. There
are examples of how to achieve
this with
relatively little configuration.
You could also extend this concept to CSS background-image properties by
simply adding a class to the <html> element:
From here, you can target the save-data class on the <html> element in your
CSS to change how images are delivered. You could send low resolution background
images to high resolution screens as shown in the above HTML example, or omit
certain resources altogether.
Some image content on the web is simply non-essential. While such imagery can
make for nice asides to content, they may not be desirable by those trying to
squeeze all they can out of metered data plans. In what is perhaps the simplest
use case of Save-Data, we can use the PHP detection code from earlier and omit
non-essential image markup altogether:
<p>This paragraph is essential content. The image below may be humorous, but it's not critical to the content.</p> <?php if($saveData===false){ // Only send this image if `Save-Data` hasn't been detected. ?><imgsrc="meme.jpg"alt="One does not simply consume data."><?php }
This technique can certainly have a pronounced effect, as you can see in the
figure below:
A comparison of non-critical imagery being loaded when Save-Data is
absent, versus that same imagery being omitted when Save-Data is
present.
Of course, omitting images isn't the only possibility. You can also act on
Save-Data to forego sending other non-critical resources, such as certain
typefaces.
While web fonts don't usually make up nearly as much of a given page's total
payload as images often do, they're still quite popular. They don't consume an
insignificant amount of
data, either.
Furthermore, the way browsers fetch and render fonts is more complicated than
you might think, with concepts such as
FOIT,
FOUT, and browser
heuristics making rendering a nuanced operation.
It might stand to reason then that you might want to leave out non-essential web
fonts for users who want leaner user experiences. Save-Data makes this a
reasonably painless thing to do.
For example, let's say you've included Fira
Sans from Google
Fonts on your site. Fira Sans is an excellent body
copy font, but maybe it isn't so crucial to users trying to save data. By adding
a class of save-data to the <html> element when the Save-Data header is
present, we can write styles that invoke the non-essential typeface at first,
but then opts out of it when the Save-Data header is present:
/* Opt into web fonts by default. */ p, li{ font-family:'Fira Sans','Arial', sans-serif; }
/* Opt out of web fonts if the `save-Data` class is present. */ .save-data p, .save-data li{ font-family:'Arial', sans-serif; }
Using this approach, you can leave the <link> snippet from Google Fonts in
place, because the browser speculatively loads CSS resources (including web
fonts) by first applying styles to the DOM, and then checking if any HTML
elements invoke any of the resources in the style sheet. If someone happens by
with Save-Data on, Fira Sans will never load because the styled DOM never
invokes it. Arial will kick in, instead. It's not as nice as as Fira Sans, but
it may be preferable to those users trying to stretch their data plans.
If you're comfortable using server push and understand its current, quirky way
of interacting with the browser cache, then great. But you may want to consider
disabling it altogether if the Save-Data header is present.
Many HTTP/2 implementations kick off a server push for a resource when a Link
response header invoking rel=preload is set.
This leads to some confusion as to whether rel=preload and server push are one
and the same, but they're two distinct things. rel=preload is a resource hint,
and server push is part of HTTP/2. It just so happens the Link header kicks
off a server push in a number of HTTP/2 implementations.
The specification for rel=preloadaddresses this potential pain
point by offering a nopush
keyword to be used in Link HTTP response headers. Using the back end
detection logic outlined earlier, you could append nopush if Save-Data is
present:
// `preload` like usual… $preload="</css/styles.css>; rel=preload; as=style";
if($saveData===true){ // …but don't push anything if `Save-Data` is detected! $preload.="; nopush"; }
header("Link: ".$preload);
There are other ways to achieve
this, some more more nuanced
than others, but the idea is the same: HTTP/2 server push is turned off when
Save-Data is present.
As you can see, there's a lot that can be accomplished with Save-Data. These
are just a couple simple use cases to get you going, so feel free to experiment
and see what novel use cases you can come up with!
The Save-Data header does not have much nuance; it is either on or off, and
the application bears the burden of providing appropriate experiences based on
its setting, regardless of the reason.
For example, some users might not allow data saving mode if they suspect there
will be a loss of app content or function, even in a poor connectivity
situation. Conversely, some users might enable it as a matter of course to keep
pages as small and simple as possible, even in a good connectivity situation.
It's best for your app to assume that the user wants the full and unlimited
experience until you have a clear indication otherwise via an explicit user
action.
As site owners and web developers, let's take on the responsibility of managing
our content to improve the user experience for data- and cost-constrained users.