Connection-Aware Components

Over the last decade, we have learned to embrace the uncertainty of developing for the web.

We don’t design sites for specific screen dimensions anymore, we make them responsive. We don’t assume ideal browsers and devices, we use progressive enhancement. When it comes to connectivity though, we still treat that as a binary choice: you’re either on- or offline.

Real connections are not that simple. Depending on your location, network condition or data plan, speeds can range from painfully slow to blazingly fast. The concept of “online” can be a drastically different experience for different users, especially on mobile.

What if there was a way to adapt websites based on our users connections, just like we do for varying display widths and browser capabilities? The Network Information API might enable us to do so.

The Network Information API

This API is an editor’s draft by the WICG and currently available in Chrome. It can be accessed through the read-only property navigator.connection (MDN), which exposes several properties that provide information about a user’s current connection:

  • connection.type:

Returns the physical network type of the user agent as strings like “cellular”, “ethernet” or “wifi”.

  • connection.downlink:

An effective bandwidth estimate (in Mb/s), based on recently observed active connections.

  • connection.rtt:

An estimate of the average round-trip time (in milliseconds), based on recently observed active connections.

  • connection.saveData:

Returns true if the user has requested “reduced data mode” in their browser settings.

  • connection.effectiveType:

This is a combined estimation of the network quality, based on the round-trip time and downlink properties. It returns a string that describes the connection as either: slow-2g, 2g, 3g or 4g. Here’s how these categories are determined:

Responding to Changes

There is also an Event Listener available on the connection property that fires whenever a change in the network quality is detected:

function onConnectionChange() {
    const { rtt, downlink, effectiveType } = navigator.connection
    console.log(`Round Trip Time: ${rtt}ms`)
    console.log(`Downlink Speed: ${downlink}Mb/s`)
    console.log(`Effective Type: ${effectiveType}`)
}
navigator.connection.addEventListener('change', onConnectionChange)

Support

can I use support table for the network information API

👉 Be aware that all of this is still experimental. Only Chrome and Samsung Internet browsers have currently implemented the API. It’s a very good candidate for progressive enhancement though - and support for other platforms is on the way.

Connection-aware components

So how could this be used? Knowing about connection quality enables us to custom-fit resources based on network speed and data preferences. This makes it possible to build an interface that dynamically responds to the user’s connection - a “connection-aware” frontend.

By combining the Network Information API with React, we could write a component that renders different elements for different speeds. For example, a <Media /> component in a news article might output:

  • offline: a placeholder with alt text
  • 2g / reduced data mode: a low-resolution image, ~30kb
  • 3g: a high resolution retina image, ~200kb
  • 4g: a HD video ~1.8MB
a media component, showing four different states of an image or video of a chameleon
The different states of our Media component

Here’s a (very simplified) example of how that might work:

class ConnectionAwareMedia extends React.Component (
    constructor(props) {
        super(props)
        this.state = {
            connectionType: undefined
        }
    }

    componentWillMount() {
        // check connection type before first render.
        if (navigator.connection && navigator.connection.effectiveType) {
            const connectionType = navigator.onLine 
                ? navigator.connection.effectiveType
                : 'offline'
            this.setState({
                connectionType
            })
        }
    }

    render() {
        const { connectionType } = this.state
        const { imageSrc, videoSrc, alt } = this.props

        // fallback if network info API is not supported.
        if (!connectionType) {
            return <Image src={imageSrc.hires} alt={alt} />
        }

        // render different subcomponents based on network speed.
        switch(connectionType) {
            case 'offline':
                return <Placeholder caption={alt} />

            case '4g':
                return <Video src={videoSrc} />

            case '3g':
                return <Image src={imageSrc.hires} alt={alt} />

            default:
                return <Image src={imageSrc.lowres} alt={alt} />
        }
    }
)

Using a Higher-Order Component

The above example makes our component a bit unpredictable - it renders different things, even when given the same props. This makes it harder to test and maintain. To simplify it and enable reuse of our logic, moving the network condition check into a separate higher-order component might be a good idea.

Such a HoC could take in any component we want and make it connection-aware, injecting the effective connection type as a prop.

function withConnectionType(WrappedComponent, respondToChange = false) {
    return class extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                connectionType: undefined
            }
            // Basic API Support Check.
            this.hasNetworkInfoSupport = Boolean(
                navigator.connection && navigator.connection.effectiveType
            )
            this.setConnectionType = this.setConnectionType.bind(this)
        }

        componentWillMount() {
            // Check before the component first renders.
            this.setConnectionType()
        }

        componentDidMount() {
            // optional: respond to connectivity changes.
            if (respondToChange) {
                navigator.connection.addEventListener(
                    'change', 
                    this.setConnectionType
                )
            }
        }

        componentWillUnmount() {
            if (respondToChange) {
                navigator.connection.removeEventListener(
                    'change', 
                    this.setConnectionType
                )
            }
        }

        getConnectionType() {
            const connection = navigator.connection
            // check if we're offline first...
            if (!navigator.onLine) {
                return 'offline'
            }
            // ...or if reduced data is preferred.
            if (connection.saveData) {
                return 'saveData'
            }
            return connection.effectiveType
        }

        setConnectionType() {
            if (this.hasNetworkInfoSupport) {
                const connectionType = this.getConnectionType()
                this.setState({
                    connectionType
                })
            }
        }

        render() {
            // inject the prop into our component.
            // default to "undefined" if API is not supported.
            return (
                <WrappedComponent
                    connectionType={this.state.connectionType}
                    {...this.props}
                />
            )
        }
    }
}

// Now we can reuse the function to enhance all kinds of components.
const ConnectionAwareMedia = withConnectionType(Media)

👉 This small proof-of concept is also available on CodePen, if you want to play around.

Further Reading

Webmentions

  1. Nice tech, but it assumes that people with LTE have unlimited data plans, at least here in Germany it is often limited and I would not want to waste it on videos unless I opt in. And the ‘data saver’ features don’t seem to work with the Browser APIs in that sense or do they?

    Matthias Götzke on Twitter
  2. The API does provide a connection.saveData property that can help with that. you could serve users in data saver mode smaller assets per default, and include an option to show the video if they prefer.

    Max Böck on Twitter
  3. Be interesting to test this from a CRO perspective.

    Lewis Pugsley on Twitter
  4. If i am used ssr, what now?

    Sexy Anatoly on Twitter
  5. client hint headers might be available in the future. there’s currently a feature flag in chrome: chromestatus.com/feature/540790…

    Max Böck on Twitter
  6. 3g in good rf conditions can be faster than 4G in bad rf conditions, this idea is shit. always show an image with a video link.

    SΞbastien F4GRX on Twitter