103 Early Hints: The Speed Secret. Supercharge Your website SEO - Part II

103 Early Hints: The Speed Secret. Supercharge Your website SEO - Part II

In my previous post, I briefly described what 103 Early Hints is and how it can impact website loading speed and, consequently, SEO.

I demonstrated this using my own website (https://andrewbus.com) as an example, which may not be the best case study since it’s already very lightweight and optimized (I created it with speed in mind from the start).

Even with a site as optimized as mine, 103 Early Hints still improved the already high web performance parameters. In my case, Web Core Vitals increased from 96-98 to 100. If your web performance is around 60-70, the benefits of implementing 103 Early Hints could be even greater.

You can read that article here: 103 Early Hints: The Speed Secret.

How to approach this?

As promised in my previous article, I’ll describe in detail, using my own example, how anyone can implement 103 Early Hints on their site.

There are a few conditions that need to be met to achieve this. There are also some things that need to be implemented manually, sometimes requiring server changes (unfortunately, not every server supports this, but there’s a workaround for that too).

Finally, I’ll share a plugin written by Greg, who collaborates with Astro Framework and Netlify and Vercel hosting. Greg is my brother, and an amazing programmer , and when he heard I was implementing 103 Early Hints on my site, he decided to make life easier for everyone using Astro and wrote this plugin, available on his GitHub and hopefully soon on the Astro.build website as well.

Requirements

As I mentioned earlier, implementing 103 Early Hints requires certain settings; the plugin or code alone isn’t enough, unfortunately. Not every web server currently supports it.

I’m sorry to disappoint anyone using the popular nginx, but it currently doesn’t have stable support for 103 Early Hints.

Servers like Apache or H2O fortunately support it and only require minor configuration. You can read more about this here:

What if I have nginx? Can I not use 103?

Fortunately, there’s also a solution for those using nginx or other servers that don’t support 103 Early Hints.

CloudFlare comes to the rescue here, with a fairly new service that supports Early Hints.

So how to set it up?

Step I - Server Setup

The first step is to configure your server to support 103 Early Hints. I’ve provided links above on how to do this for Apache and H2O.

How to use CloudFlare?

Thanks to CloudFlare’s introduction of 103 Early Hints support, the availability of this functionality has greatly increased.

To set up any server to support 103 using CloudFlare, you need to change your domain’s DNS to CloudFlare’s.

CloudFlare DNS Panel

Next, of course, set up the A (CNAME) records in CloudFlare to point to your server, so the domain continues to work with your server.

CloudFlare DNS Proxy

Now in CloudFlare, you need to select DNS proxying. A quick note: if you’re using Vercel or Netlify for hosting, you might encounter a small problem here, like a “wrong configuration error”. If you’ve run into this problem, read this post: How to Fix the Incorrect Configuration Error in Vercel with Cloudflare, where I explain how to deal with it.

Once DNS proxying has been activated and is working correctly, the next thing we need to do is enable 103 Early Hints in CloudFlare. You can find this in __Your Website -> Speed -> Optimization -> Early Hints:

CloudFlare DNS Proxy

And that’s it for the server side. Now it’s time for Step II, the website code.

Step II - Programming the Website

I’ll divide this step into elements as well, since there are different solutions for different platforms, languages, and frameworks.

Astro and Netlify/Vercel

So if you’re using Astro Framework and hosting your site on Vercel, all you need to do is download and install Greg’s plugin (middleware), available here: Astro Vercel 103 Early Hints Middleware

If you are using Netlify you can use my fork, of that middleware:

This plugin will automatically detect CSS that you set in the HTML header as “preload”, and images that you set as “eager” loading and “priority fetch”, and add them to the Vercel/Netlify configuration file, thus setting the server response header. This server response header is used by CloudFlare to generate the 103 Early Hints response.

If you’re hosting your site elsewhere, look for a config analogous to what Vercel has and set something like this:

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Link",
          "value": "</_astro/ai-for-seo-essentials.BV5AZERD.css>; rel=\"preload\"; as=\"style\", </_astro/hero-background.BKM9Jnh6_1TaUIt.webp>; rel=\"preload\"; as=\"image\""
        }
      ]
    }
  ]
}

Of course, remember that in Astro, CSS/JS file names may change with subsequent builds, so it’s best to write a workflow that will automatically update this for you.

NextJS and Vercel or Netlify

I’m working on a plugin for NextJS that I’ll try to make available as soon as possible. Until then, you simply need to do what I described above, which is to set up your Vercel or Netlify config like this:

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Link",
          "value": "</_astro/ai-for-seo-essentials.BV5AZERD.css>; rel=\"preload\"; as=\"style\", </_astro/hero-background.BKM9Jnh6_1TaUIt.webp>; rel=\"preload\"; as=\"image\""
        }
      ]
    }
  ]
}

If your website isn’t in active development, this might be enough, but if you’re constantly working on it, it’s best to set up a simple script that will update these file names for you.

Any PHP, e.g., WordPress

If you’re using WordPress, you can simply add something like this to your master theme template:

header()

Here, it’s best to use whatever helpers you use to retrieve CSS names. Personally, despite really disliking WordPress, I have to work with it, so to optimize it, I wrote an automation in Gulp that retrieves all the style and script files I use in the custom theme I created, combines them, minifies them, versions them, and uploads them to a CDN. So to know what the current CSS or JS file is called, I wrote a helper that retrieves the name of the current file from a JSON generated by Gulp. Using this helper, I can easily set the HTML head response:

header("link: <" .asset_absolute_path('app.css') .">; rel=preload; as=style, "
        ."</wp-includes/js/jquery/jquery.js?ver=1.12.4>; rel=preload; as=script, "
        ."<https://use.typekit.net/pmf0lsg.css>; rel=preload; as=style, "

So I suggest doing something similar on your end.

I don’t plan on writing a WordPress plugin for this because I really don’t like that environment, and besides, I’m strongly against plugins in WordPress. From my experience, I can say that plugins in WordPress should be avoided at all costs. If they implement some simple functionality, it’s better to write it yourself than to use a universal plugin, because every plugin slows down the already very slow WordPress. Every plugin, even if it only does a small thing on a single page, is activated by WordPress on every page and causes a noticeable performance drop.

Laravel

A similar solution can be applied to Laravel. I’ll try to write some plugin for Laravel or Vite soon that will set this up automatically. Until then, you can simply write custom code in the style I described above for WordPress.

Python

I won’t describe too much here, as everything I wrote above is also true for Django or Flask. In any case, you can do it like this, for example:

Flask:

from flask import Flask, Response

app = Flask(__name__)

@app.route('/')
def hello_world():
    link_header = (
        f'<{asset_absolute_path("app.css")}>; rel=preload; as=style, '
        f'</wp-includes/js/jquery/jquery.js?ver=1.12.4>; rel=preload; as=script, '
        f'<https://use.typekit.net/pmf0lsg.css>; rel=preload; as=style'
    )
    
    response = Response("Hello, World!")
    response.headers['Link'] = link_header
    return response

if __name__ == '__main__':
    app.run()

Django:

from django.http import HttpResponse

def my_view(request):
    response = HttpResponse("Hello, World!")
    
    link_header = (
        f'<{asset_absolute_path("app.css")}>; rel=preload; as=style, '
       f'</wp-includes/js/jquery/jquery.js?ver=1.12.4>; rel=preload; as=script, '
        f'<https://use.typekit.net/pmf0lsg.css>; rel=preload; as=style'
    )
    
    response['Link'] = link_header
    return response

Of course, no one in their right mind would use jQuery in Django/Flask; I just used an example from my WordPress project.

How to check if it’s working

You can check this using my page, for example:

My Basic 103 Early Hints Checker

or use other websites that also offer a checker, e.g. which will give you a littel more info:

103 Early Hints Checker

You can also check this in your terminal

curl -X HEAD -I https://andrewbus.com/

If you get something like this in response:

HTTP/2 103
link: </_astro/ai-for-seo-essentials.BV5AZERD.css>; as=style; rel=preload, </_astro/hero-background.BKM9Jnh6_1TaUIt.webp>; as=image; rel=preload

Then of course it means everything is working. If the first thing you see in the response is something other than HTTP/2 103, e.g., something like this:

HTTP/2 200
date: Tue, 13 Aug 2024 13:31:59 GMT
content-type: text/html; charset=utf-8
access-control-allow-origin: *
age: 67980
cache-control: public, max-age=0, must-revalidate
content-disposition: inline
link: </_astro/ai-for-seo-essentials.BV5AZERD.css>; rel="preload"; as="style", </_astro/hero-background.BKM9Jnh6_1TaUIt.webp>; rel="preload"; as="image"
strict-transport-security: max-age=0
x-vercel-cache: HIT
x-vercel-id: iad1::zf4m2-1723555919178-3cdfe5c61ea7

Then unfortunately it means it’s not working.

Summary

I hope I’ve presented clearly and in sufficient detail how you can set up 103 Early Hints on your site, including server configuration or using DNS proxy with CloudFlare, and how to adapt your web application code to handle this response.

I tried to show this using examples covering the most popular platforms, but if you didn’t find an answer on how to do this in your project based on other technologies, write in the comments and I’ll try to help you.