Docusaurus: improving Core Web Vitals with fetchpriority
By using fetchpriority on your Largest Contentful Paint you can improve your Core Web Vitals. This post implements that with Docusaurus v2. There is a follow on post that details migrating this plugin to Docusaurus v3.
Avoiding lazy loading on the Largest Contentful Paint
At the weekend I wrote a post documenting how I believe I ruined the SEO on my blog. That post ended up trending on Hacker News. People made suggestions around things I could do that could improve things. One post in particular caught my eye from Growtika saying:
Page speed: It's one of the most important ranking factor. You don't have to get 100 score, but passing the core web vitals score and having higher score on mobile is recommended.
https://pagespeed.web.dev/report?url=https%3A%2F%2Fjohnnyreilly.com%2F&form_factor=mobile
A cool trick to improve the result fast is by removing the lazy load effect from the LCP:
Another person chimed in with:
Indeed. Even better, making it high priority instead of normal: https://addyosmani.com/blog/fetch-priority/
fetchpriority
I hadn't heard of fetchpriority before this, but the linked article by Addy Osmani carried this tip:
Add fetchpriority="high" to your Largest Contentful Paint (LCP) image to get it to load sooner. Priority Hints sped up Etsy’s LCP by 4% with some sites seeing an improvement of up to 20-30% in their lab tests. In many cases, fetchpriority should lead to a nice boost for LCP.
I was keen to try this out. Somewhat interestingly, I was the person responsible for originally contributing lazy loading to Docusaurus. For what it's worth, lazy loading is a good thing to do. It's just that in this case, it was causing the LCP to be lazy loaded. I wanted to change that.
Swizzling the image component
Since my initial contribution, the implementation had been tweaked to allow user control via Swizzling. By the way, swizzling is a great feature of Docusaurus. It allows you to override the default implementation of a component. In this case, I wanted to override the Img component and opt out of lazy loading. I did this by running the following command:
This created a file at src/theme/MDXComponents/Img.js. I then made the following change:
Getting rid of the loading="lazy" attribute was all I needed to do. This gets us to the point where none of our images are lazy loaded anymore. Stage 1 complete!
Adding fetchpriority="high" to the LCP with a custom plugin
The next thing to do was to write a small Rehype plugin to add fetchpriority="high" to the LCP. I did this by creating a new JavaScript file called image-fetchpriority-rehype-plugin.js:
The above plugin runs over the AST of the MDX file and adds fetchpriority="high" to the first image. It also adds loading="eager" to the first image and loading="lazy" to all other images.
Interestingly, when I was writing it I discovered that the visitor is invoked multiple times for the same elements. I'm not quite sure why, but the logic in the plugin uses a Map to keep track of which images have already been processed. TL;DR it works!
I then added the plugin to the docusaurus.config.js file:
What does it look like when applied?
Now we have this in place, if we run the same test with pagespeed we have different results:
We're now not lazy loading the image and we're also making it a high priority fetch. Great news!
I'd like for this to be the default behaviour for Docusaurus. I'm not sure if it's possible to do this in a way that's straightforward. I've raised an issue on the Docusaurus repo to see if it's possible.
Discussion in the ATmosphere