Today, I would like to share with you some HTTP headers that are not fully implemented or specified but could lead to a simpler CDN configuration and better performance.

This post will cover:

Simplify adaptive design with Client Hints request header and Key response header

In a mobile world, adaptive and responsive design becomes a critical part of the user experience. Designers want to deliver optimized pages and static resources based on navigation contexts.

Adaptive design will (theoretically) ensure the best user experience according to whichever device the user is using. Unlike responsive design, where a screen “flows” from desktop design into a smaller device’s, adaptive design offers tailor-made solutions. As the name suggests, they adapt to the user’s situational needs and capabilities. As designers, we can show users that we’re in adequation with their needs on a mobile device by making our design touch friendly. Meanwhile, we can do the same for desktop users.

A bit of Theory

When we want to choose the right version of the page (i.e content negotiation), we traditionally need to rely on HTTP request headers. We often need to parse the User-Agent header to determine if the device is a mobile, a tablet or a desktop. We may use regular expressions or databases indexing millions of User-Agent string for getting information about the device capabilities. This technique partially works but is costly in terms of maintenance.

In conjunction with the User-Agent header, it’s possible to leverage on the Accept, Accept-Encoding and Accept-Language headers to determine the right resource that should be served to the browser. This is for instance the case for the Webp or JPEGX negotiation.

  Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
  Accept-Encoding: gzip, deflate, br
  Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7

Those three headers won’t give more information about connectivity, image size, window size, etc.

Of course, for static resources like images, there are alternative like srcset attributes and picture tags that let the browser select different URLs for each version of an image. The problem is now in the hand of the web developer. This could be manageable with the right build chain but tends to become hard to automate at scale.

This is why Client Hints have been specified :

Client-Hints is a mechanism for proactive content negotiation: client advertises some set of hints via HTTP request header, server uses provided hints to adapt the served resource – as such, it’s a generic mechanism that can be extended to any amount of use cases. That said, one of the primary motivators for CH today is to help automate (where possible) the negotiation of optimal resolution and size of delivered image resources.

An excellent article describes the Client Hints in more details.

Let’s look at an example :

Chrome advertises its support for the WebP format via the Accept request header.

The next three request headers are the client-hint headers advertising the device pixel ratio of the client’s device (3x), the layout viewport width (460px), and the intended display width of the resource (230px). This provides all the necessary information to the server to select the optimal image variant based on its own set of policies: availability of pre-generated resources, cost of re-encoding or resizing a resource, popularity of a resource, current server load, and so on. In this particular case, the server uses the DPR and Width hints and returns a WebP resource, as indicated by the Content-Type, Content-DPR and Vary headers.

Now that we are able to choose the right version of the resource based on request header, let’s see the complementary problem : how do we store multiple versions of a resource under the same URL, specifically in a CDN.

HTTP defines the Vary header for this use case : The Vary header is an HTTP response header that specifies variations based on request headers. Vary’s operation is generic; it works well when caches understand the semantics of the selected headers like Accept-Encoding or Accept-Language. However, when the value could be almost everything like User-Agent or Viewport-Width, it falls short.

Practically, CDN adopts defensive behavior against this Vary header to keep high cache HIT ratio. They either ignore it or accept it for some headers after normalizing it to reduce the variation count.

Similarly to Client Hints that extend Accept-* headers, Key Header has been specified to improve the Vary one.

For example, this response header field:

Key: cookie;param=_sess;param=ID

indicates that the selected response depends upon the “_sess” and “ID” cookie values.

This Key:

Key: user-agent;substr=MSIE

indicates that there are two possible secondary cache keys for this resource; one for requests whose User-Agent header field contains “MSIE”, and another for those that don’t.

Client Hints may be combined with Key response header field to enable fine-grained control of the cache key for improved cache efficiency. For example, the server can return the following set of instructions:

Key: DPR;partition=1.5:2.5:4.0

Above example indicates that the cache key needs to include the value of the DPR header field with three segments: less than 1.5, 1.5 to less than 2.5, and 4.0 or greater.

Simplify server and CDN monitoring with Server Timing

Monitoring pages load time has been more and more important, especially Real User Monitoring. Also, it’s interesting to be able to follow the response time end to end. We need to understand where has been spent the time from the initial request to the full render of the page. Navigation Timing, Resource Timing and User Timing are heavily used by RUM solutions to be able to follow what happens in the browser and network side. Server Timing tries to complete the whole by giving the timing details spend in the back-end. This job is often done by dedicated agents like NewRelic or Dynatrace.

We already can add Server-Timing in the response header, like so:

Server-Timing 'cpu=0.009; "CPU", mysql=0.005; "MySQL", filesystem=0.006; "Filesystem"'

This will be displayed by Chrome in the DevTools:

What is particularly interesting is the fact that Server Timing entries are included on Resource- and NavigationTiming entries as serverTiming. They must have a name, might have a non-empty description, and will likely have a non-zero duration.

performance.getEntriesByName(<path/to/resource1>)[0].serverTiming

We can send this data to any RUM service. CDN could change the famous X-Cache:HIT by ServerTiming 'cdn=30; "CDN HIT"'.

Conclusion

In this article, we are living in the future since those headers are not widely implemented. I hope to see more support for those headers in the future on CDN, web servers and browsers. They are others HTTP headers that are interesting for the webperf but are not described in this article: cache-control: immutable, link rel=preload for HTTP/2 PUSH.

Let me know in the comments how you consider those headers, what you think that will be really interesting for your use cases.

ABOUT THE AUTHOR

Anthony Barré (@AnthoBarre) is software engineer at Fasterize, a webperf automation solution.

3 Responses to “Promising HTTP Headers for Web Performance”

  1. Charles Vazac

    Great post, Anthony!

    One note, the format for the server timing response header has evolved a bit, see here: https://w3c.github.io/server-timing/.

    So this:

    Server-Timing 'cpu=0.009; "CPU", mysql=0.005; "MySQL", filesystem=0.006; "Filesystem"'

    Should be:

    Server-Timing: cpu; dur=0.009; desc=CPU, mysql; dur=0.005; desc=MySQL, filesystem; dur=filesystem; desc=Filesystem

    And this:

    ServerTiming 'cdn=30; "CDN HIT"'

    Should be:

    Server-Timing: cdn; dur=30; desc="CDN HIT"

  2. Etienne Bruines

    Great to see I wasn’t the only one, Charles.

    Unfortunately, the Chrome Devtools only show it the way Anthony described. At least so in my build (64.0.3251.0).

  3. Jon Arne Sæterås

    Thank you Anthony for a great post with an important message.
    I’ve been working with this problem domain for a while now, and it has resulted in fully fledged CDN with support of client hints and mechanisms to avoid cache pollution. The CDN (http://imageengine.io) use Client Hints (and device detection) to resize and optimize images according to the capabilities of the requesting agent. Challenge is of course to cache the derivative images correctly. To our experience the Key header is not ideal because it only offers “buckets” or ranges as well as simple “boolean” functionality. With the diversity we see on the modern web, this is not good enough because either a too heavy image is served, or the visual quality is bad. I had high hopes for the “Variants” header, but seems like this too is made for a different use case. We ended up creating our own more efficient caching scheme and moving much of the image optimization logic to the edges of the CDN.