In the previous blog, we learned how a browser downloads many scripts and useful resources to render a webpage. But not all of them are necessary to show a page’s content. Because of this, the page rendering is delayed. However, most of them will be needed as the user navigates through the website’s various pages.
In this article, we’ll learn to identify such resources and classify them as critical and non-critical. Once identified, we’ll inline the critical resources and defer the non-critical resources.
For this blog, we’ll use the following tools:
- Google Lighthouse and other Chrome DevTools to identify render-blocking resources.
- Webpack and CRACO to fix it.
Other configurations are as follows:
- Create React App v4.0
- Formik and Yup for handling form validations
- Font Awesome and Bootstrap
- Lazy loading and code splitting using Suspense, React lazy, and dynamic import
- ngrok and serve for serving build
A render-blocking resource typically refers to a script or link that prevents a browser from rendering the processed content.
Lighthouse will flag the below as render-blocking resources:
- A <script> tag in <head> that doesn’t have a defer or async attribute.
- A <link rel=”stylesheet”> tag that doesn’t have a media attribute to match a user's device or a disabled attribute to hint browser to not download if unnecessary.
- A <link rel=”import”> that doesn’t have an async attribute.
Identifying Render-Blocking Resources
To reduce the impact of render-blocking resources, find out what’s critical for loading and what’s not.
To do that, we’re going to use the Coverage Tab in Chrome DevTools. Follow the steps below:
1. Open the Chrome DevTools (press F12)
2. Go to the Sources tab and press the keys to Run command
The below screenshot is taken on a macOS.
3. Search for Show Coverage and select it, which will show the Coverage tab below. Expand the tab.
4. Click on the reload button on the Coverage tab to reload the page and start instrumenting the coverage of all the resources loading on the current page.
5. After capturing the coverage, the resources loaded on the page will get listed (refer to the screenshot below). This will show you the code being used vs. the code loaded on the page.
The list will display coverage in 2 colors:
a. Green (critical) - The code needed for the first paint
b. Red (non-critical) - The code not needed for the first paint.
After checking each file and the generated index.html after the build, I found three primary non-critical files -
a. 5.20aa2d7b.chunk.css - 98% non-critical code
b. https://use.fontawesome.com/3ec06e3d93.js - 69.8% non-critical code. This script loads below CSS -
1. font-awesome-css.min.css - 100% non-critical code
2. https://use.fontawesome.com/3ec06e3d93.css - 100% non-critical code
c. main.6f8298b5.chunk.css - 58.6% non-critical code
The above resources satisfy the condition of a render-blocking resource and hence are prompted by the Lighthouse Performance report as an opportunity to eliminate the render-blocking resources (refer screenshot). You can reduce the page size by only shipping the code that you need.
Once you’ve identified critical and non-critical code, it is time to extract the critical part as an inline resource in index.html and deferring the non-critical part by using the webpack plugin configuration.
For Inlining and Preloading CSS:
Use html-critical-webpack-plugin to inline the critical CSS into index.html. This will generate a <style> tag in the <head> with critical CSS stripped out of the main CSS chunk and preloading the main file.
Once done, create a build and deploy. Here’s a screenshot of the improved opportunities:
To use CRACO, refer to its README file.
For Deferring Routes/Pages:
Use lazy-loading and code-splitting techniques along with webpack’s magic comments as below to preload or prefetch a route/page according to your use case.
The magic comments enable webpack to add correct attributes to defer the scripts according to the use-case.
For Deferring External Scripts:
For those who are using a version of webpack lower than 5, use script-ext-html-webpack-plugin or resource-hints-webpack-plugin.
I would recommend following the simple way given below to defer an external script.
The defer and async attributes can be specified on an external script. The async attribute has a higher preference. For older browsers, it will fallback to the defer behaviour.
If you want to know more about the async/defer, read the further reading section.
Along with defer/async, we can also use media attributes to load CSS conditionally.
It’s also suggested to load fonts locally instead of using full CDN in case we don’t need all the font-face rules added by Font providers.
Now, let’s create and deploy the build once more and check the results.
The opportunity to eliminate render-blocking resources shows no more in the list.
We have finally achieved our goal!
The above configuration is a basic one. You can read the libraries’ docs for more complex implementation.
Let me know if this helps you eliminate render-blocking resources from your app.
If you want to check out the full implementation, here’s the link to the repo. I have created two branches—one with the problem and another with the solution. Read the further reading section for more details on the topics.
Hope this helps.