Smaller Documents for Smaller Screens using Sec-CH-Viewport-Width

lilouartz

Lilou Artz

Posted on June 27, 2024

Smaller Documents for Smaller Screens using Sec-CH-Viewport-Width

If you have been following my engineering blog, you know that I am obsessive about performance. Pillser lists a lot of data about supplements and research papers, and I want to make sure that the website is fast and responsive. One of the ways I've done it is by using Sec-CH-Viewport-Width to determine the width of the viewport and serve smaller documents to mobile devices.

What is Sec-CH-Viewport-Width?

Sec-CH-Viewport-Width is a Client Hints (CH) header to convey the viewport width of a client's display in CSS pixels. This header allows web servers to adapt their responses based on the actual size of the user's viewport, enabling better optimization of resources like images and layout.

However, by default, the header is not sent by the browser. To enable it, you need to send HTTP response headers with Accept-CH: Sec-CH-Viewport-Width. This will instruct the browser to send the Sec-CH-Viewport-Width header in the subsequent requests.

How does Pillser use Sec-CH-Viewport-Width?

If you look at pages like the supplement search or a specific supplement category page, you will notice thatΒ (on desktop devices) there is a lot of tabular data being displayed. This data provides valuable information for someone researching supplements, but it is not very readable on mobile devices and it accounts for a lot of the page's weight.

To solve this problem, Pillser uses Sec-CH-Viewport-Width to determine the width of the viewport and serve smaller documents to mobile devices. It works just like CSS media queries, but instead of deciding which content to display on a device, it makes the decision on the server. Here is the implementation of useViewportWidth:

import { usePublicAppGlobal } from './usePublicAppGlobal';
import { useEffect, useState } from 'react';

export const useViewportWidth = () => {
  const publicAppGlobal = usePublicAppGlobal();

  const [width, setWidth] = useState<number | null>(
    publicAppGlobal.visitor.viewportWidth,
  );

  useEffect(() => {
    const handleResize = () => {
      setWidth(window.innerWidth);
    };

    window.addEventListener('resize', handleResize);

    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return width;
};
Enter fullscreen mode Exit fullscreen mode

On the server, I parse the Sec-CH-Viewport-Width header and populate the visitor.viewportWidth field in the public app global. This field is then used by the useViewportWidth hook to determine the width of the viewport. Here is the server-side logic:

let viewportWidth: number | null;

try {
  viewportWidth = z
    .number({ coerce: true })
    .min(1)
    .parse(request.headers.get('sec-ch-viewport-width'));
} catch {
  viewportWidth = null;
}
Enter fullscreen mode Exit fullscreen mode

And that's really all there is to it. The Sec-CH-Viewport-Width header is sent by the browser, Pillser parses it, and uses the result to determine the width of the viewport. This allows Pillser to serve smaller documents to mobile devices, improving the user experience and reducing the page weight.

Gotchas

Two gotchas to be aware of: browser support and the initial render.

Today, Client Hints are supported by 76% of browsers. The primary browsers that do not support Client Hints are Safari and Firefox. Regarding, Safari iOS, since we are defaulting to the smallest size in absence of the header (see the next section), it is not a problem. As for Safari desktop and Firefox, the website will still work as expected, but it will need to recalculate the content on the client-side. That's a fine trade-off if it means that the majority of visitors will get improved experience.

(You can also add support to Safari and Firefox by implementing pseudo-Client Hints by using cookies to set the viewport width.)

The other gotcha to be aware of is that the browser will only send the Sec-CH-Viewport-Width header in subsequent requests, not in the response. This means that the first time a user visits a page, their viewport width will not be known. To fix this, I default to always using the smallest breakpoint when the viewport width is unknown. This way, the mobile devices will still get the correct content, but the desktop UI will be updated upon recalculating the viewport using client-side logic.

πŸ’– πŸ’ͺ πŸ™… 🚩
lilouartz
Lilou Artz

Posted on June 27, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related