Speculation Rules API on Akamai

This blog post covers how to get started with the brand new browser Speculation Rules API on Akamai.

This Web API aims to improve the performance of Multi Page Apps (MPA) by proactively prefetching or prerendering future navigations.

A few Property Manager Behaviors or an EdgeWorker are all you need to benefit from Speculation Rules.

Results

After talking to the friendly Google folks, we tested an Akamai prototype of the speculation rules API.
We enabled it for production traffic from Scalemates.com, the largest modeling website in the world. It is powered by Akamai (Ion, WAF, Image Manager) and uses mPulse to validate performance changes.

The results of this new API were impressive:

  • The P95 of LCP is around 500 ms faster
  • The P75 of LCP is around 170 ms faster
  • 59% of navigations triggered pre-rendering

We shaved 170 ms of the Largest Contentful Paint (LCP) with the blink of an eye. The API has the potential to help you with your #webperf and Core Web Vitals (CWV) goals in 2024.

The speculation rules we used trigger a prefetch when links are hovered (eagerness is moderate) and trigger a prerender when the user begins to interact with them (eagerness is conservative). CSS selectors indicate which links should trigger the prefetch and prerender actions.

<script type=speculationrules>
{ 
"prefetch":[{"source":"document","where":{"selector_matches":".pf, .ac a"},"eagerness":"moderate"}],
"prerender":[{"source":"document","where":{"selector_matches":".pf, .ac a"},"eagerness":"conservative"}] 
}
</script>

The table shows us that the Largest Contentful Paint (LCP) is 177ms faster for Prerendered pages (537 ms) compared to standard rendered pages (714 ms) at the 75th Percentile.

Akamai Implementation Options

The speculation rules are defined in a JSON structure defining:

  • Which action should the browser trigger? Prerender or prefetch?
  • Which URLs to trigger actions on? Specific URLs, CSS selectors or Regex?
  • How eager to trigger those ? As soon as possible, while hovering a link or on mouse down?

The JSON structure itself can be communicated to the browser in 2 ways:

  • Inside the the HTML with tag <script type=speculationrules>
  • An external resource referenced via the HTTP response header: Speculation-Rules: {{url}}

Notes

  • Please check Browser documentation for the latest implementation details and all nuances.
  • At the time of writing only Chromium based browsers supports the API.
  • Document Rules still require a specific opt in via Origin Trials for versions <121.
  • Non supporting browsers will ignore the speculation rules

Resources

Approach 1: Via HTTP Response Header

Step 1: Add Speculation-Rules HTTP response header on all pages

Criteria:
If File extension is one of
html, htm, php, jsp, asp, EMPTY_STRING

Behaviours:

Add the Modify Outgoing Response Header Behaviour with:

  • Action: Add
  • Header Name: Speculation-Rules
  • Header Value: "/speculationrules.json"
    (Note: The quotes are important)

Step 2: Respond to /speculationrules.json request

Criteria:
If path matches /speculationrules.json

Behaviours:
Add the Construct Response Behaviour with:

  • Status: On
  • Response Body: {{SpeculationRules JSON}} (Make not that this is without <script type=speculationrules>)
  • Response Code: 200 OK

Add the Modify Outgoing Response Header Behaviour with:

  • Action: Add
  • Header Name: Content-Type
  • Header Value: application/speculationrules+json

Add the Modify Outgoing Response Header Behaviour with:

  • Action: Add
  • Header Name: Access-Control-Allow-Origin
  • Header Value: * (or more strict)

As an alternative to the Construct Response behaviour you can also host the resource on:

  • Your own origin
  • Akamai NetStorage
  • Linode Object Storage

Approach 2: Inside the html

Alternatively you can embed the Speculation Rules directly in the HTML using Akamai EdgeWorkers. Using the native html-rewriter library you can modify the response body and append <script type=speculationrules>${RULES}</script> to the <body> element.

Code example:

import { HtmlRewritingStream } from 'html-rewriter';
import { httpRequest } from 'http-request';
import { createResponse } from 'create-response';

export async function responseProvider(request) {
	   …
       let rewriter = new HtmlRewritingStream();
       const RULES = getSpeculationRules(request);
       rewriter.onElement('body', el => {
           el.append(`<script type=speculationrules>${RULES}</script>`);
       });
       return httpRequest(originURL,options).then(response => {
           return createResponse(
               response.status,
               headers,
               response.body.pipeThrough(rewriter)
           )
       })
}

function getSpeculationRules(request){
   let rules = {} //Read locally, fetch or read from EKV
   return rules;
}

Design Choices: Header vs Inline

Although both approaches are easy to implement on Akamai, the Header approach Speculation-Rules has a few advantages over the inline approach:

CDN Cache efficiency

  • Allows A/B testing without poisoning the cache
  • Purging speculation rules does not evict HTML

Edge logic without delaying HTML

  • Serve different speculation rules based on geo/connection/user/…
  • Speculation rules delay (eg. lookup RUM data) not in critical path

Summary

The new Speculation Rules API promises Instant Navigations for Multi-Page Applications with little effort to implement on Akamai.

Note: You can also apply the same method on your origin, there is no performance benefit doing it at the Edge vs the Origin.

6 Likes