Help Your Users `Save-Data`

The breadth and depth of knowledge to absorb in the web performance space is ridiculous. At a minimum, I'm discovering something new nearly every week. Case in point: The Save-Data header, which I discovered via a Google Developers article by Ilya Grigorik.

If you're looking for the tl;dr version of how Save-Data works, let me oblige you: If you opt into data savings on the Android version of Chrome (or the Data Saver extension on your desktop device), every request that Chrome sends to a server will contain a Save-Data header with a value of On. You can then act on this header to change your site's content delivery in such a way that conserves data for the user. This is a very open-ended sort of opportunity that you're being given, so let's explore a few ways that you could act on the Save-Data header to send less bytes down the wire!

Change your image delivery strategy

I don't know if you've noticed, but images are often the largest chunk of the total payload of any given page. So perhaps the most impactful step you can take with Save-Data is to change how images are delivered. What I settled on for my blog was to rewrite requests for high DPI images to low DPI images. When I serve image sets like this on my site, I do so using the <picture> element to serve WebP images with a JPEG or PNG fallback like so:

<picture>
  <source srcset="/img/george-and-susan-1x.webp 1x, /img/george-and-susan-2x.webp 2x">
  <source srcset="/img/george-and-susan-1x.jpg 1x, /img/george-and-susan-2x.jpg 2x">
  <img src="/img/george-and-susan-1x.jpg" alt="LET'S NOT GET CRAZY HERE" width="320" height="240">
</picture>

This solution is backed by tech that's baked into modern browsers. The <picture> element delivers the optimal image format according to a browser's capabilities, while srcset will help the browser decide what looks best for any given device's screen. Unfortunately, both <picture> and srcset lacks control over which image source to serve for users who want to save data. Nor should they! That's not the job of srcset or <picture>.

This is where Save-Data comes in handy. When users visit my site and send a Save-Data request header, I use mod_rewrite in Apache to serve low DPI images in lieu of high DPI ones:

RewriteCond %{HTTP:Save-Data} =on [NC]
RewriteRule ^(.*)-2x.(png|jpe?g|webp)$ $1-1x.$2 [L]

If you're unfamiliar with mod_rewrite, the first line is a condition that checks if the Save-Data header is present and contains a value of on. The [NC] flag merely tells mod_rewrite to perform a case-insensitive match. If the condition is met, the RewriteRule looks for any requests for a PNG, JPEG or WebP asset ending in -2x (the high DPI version), and redirects such requests to an asset ending in -1x (the low DPI version).

Now comes the weird part: What happens if a user visits with Data Saver enabled, but turns it off and returns later on an unmetered (i.e., Wi-Fi) connection? Because we've surreptitiously rewritten the request for a -2x image to a -1x one, the browser will serve up the low quality version of the image from the browser cache rather than requesting the high quality version from the server. In this scenario, the user is lashed to a low quality experience until they empty their browser cache (or the cached entries expire).

So how do we fix this? The answer lies in the Vary response header. Vary instructs the browser how and if it should use a cached asset by aligning cache entries to specific header(s). Values for Vary are simply other header names (e.g., Accept-Encoding, User-Agent, et cetera). If we want the browser to cache content based on the existence of the Save-Data header, all we need to do is configure the server to send a Vary response header with a value of Save-Data like so:

<FilesMatch "\.(gif|png|jpe?g|webp)$">
  Header set Vary "Save-Data"
</FilesMatch>
<FilesMatch "\.svg$">
  Header set Vary "Accept-Encoding, Save-Data"
</FilesMatch>

In this example, I'm sending a Vary response header with a value of Save-Data any time GIF, PNG, JPEG or WebP images are requested. In the case of SVGs, I send a Vary header with a value of Accept-Encoding, Save-Data. The reason for this is because SVGs are compressible text assets. Therefore, I want to ensure that the browser (and any intermediate cache, such as a CDN) takes the value of the Accept-Encoding header into consideration in addition to the Save-Data header when deciding how to retrieve entries from the cache.

Image delivery with Data Saver on vs. off

For our trouble, we now have an image delivery strategy that helps users conserve data, and will populate the browser cache with images that won't persist if the user turns off Data Saver later on.

Of course, there are other ways you could change your image delivery in the presence of Save-Data. For example, Cory Dowdy wrote a post detailing how he uses Save-Data to serve lower quality images. Save-Data gives you lots of room to devise an image delivery strategy that makes the best sense for your site or application.

Opt out of server pushes

Server push is good stuff if you're on HTTP/2 and can use it. It allows you send assets to the client before they ever know they need them. The problem with it, though, is it can be weirdly unpredictable in select scenarios. On my site, I use it to push CSS only, and this generally works well. Although, I do tailor my push strategy in Apache to avoid browsers that have issues with it (i.e., Safari) like so:

<If "%{HTTP_USER_AGENT} =~ /^(?=.*safari)(?!.*chrome).*/i">
  Header add Link "</css/global.5aa545cb.css>; rel=preload; as=style; nopush"
</If>
<Else>
  Header add Link "</css/global.5aa545cb.css>; rel=preload; as=style"
</Else>

In this instance, I'm saying "Hey, I want you to preemptively push my site's CSS to people, but only if they're not using Safari." Even if Safari users come by, they'll still get a preload resource hint (albeit with a nopush attribute to discourage my server from pushing anything).

Even so, it would behoove us to be extra cautious when it comes to pushing assets for users with Data Saver turned on. In the case of my blog, I decided I wouldn't push anything to anyone who had Data Saver enabled. To accomplish this, I made the following change to the initial <If> header:

<If "%{HTTP:Save-Data} == 'on' || %{HTTP_USER_AGENT} =~ /^(?=.*safari)(?!.*chrome).*/i">

This is the same as my initial configuration, but with an additional condition that says "Hey, if Save-Data is present and set to a value of on, don't be pushing that stylesheet. Just preload it instead." It might not be a big change, but it's one of those little things that could help visitors to avoid wasted data if a push was in vain for any reason.

Change how you deliver markup

With Save-Data, you could elect to change what parts of the document you deliver. This presents all sorts of opportunities, but before you can embark on this errand, you'll need to check for the Save-Data header in a back end language. In PHP, such a check might look something like this:

$saveData = (isset($_SERVER["HTTP_SAVE_DATA"]) && stristr($_SERVER["HTTP_SAVE_DATA"], "on") !== false) ? true : false;

On my blog, I use this $saveData boolean in various places to remove markup for images that aren't crucial to the content of a given page. For example, one of my articles has some animated GIFs and other humorous images that are funny little flourishes for users who don't mind them. But they are heavy, and not really necessary to communicate the central point of the article. I also remove the header illustration and a teeny tiny thumbnail of my book from the nav.

Image markup delivery with Data Saver on and off

From a payload perspective, this can certainly have a profound effect:

The potential effects of Data Saver on page payload

Another opportunity would be to use the aforementioned $saveData boolean to put a save-data class on the <html> element:

<html class="<?php if($saveData === true) : echo("save-data"); endif; ?>">

With this class, I could then write styles that could change what assets are used in background-image properties, or any CSS property that references external assets. For example:

/* Just a regular ol' background image */
body {
  background-image: url("/images/bg.png");
}

/* A lower quality background image for users with Data Saver turned on */
.save-data body {
  background-image: url("/images/bg-lowsrc.png");
}

Markup isn't the only thing you could modify the delivery of. You could do the same for videos, or even do something as simple as delivering fewer search results per page. It's entirely up to you!

Conclusion

The Save-Data header gives you a great opportunity to lend a hand to users who are asking you to help them, well, save data. You could use URL rewriting to change media delivery, change how you deliver markup, or really just about anything that you could think of.

How will you help your users Save-Data? Leave a comment and let your ideas be heard!


Cover of Web Performance in Action

Jeremy Wagner is the author of Web Performance in Action, available now from Manning Publications. Use promo code sswagner to save 42%.

Check him out on Twitter: @malchata