Introduction
A website’s initial loading speed can significantly impact user experience and SEO. Slow loading times can bother many users.
To address this issue, I’ve developed a new Webpack plugin called html-webpack-preload-react-router
. This plugin improves the initial loading speed of a website by reading the react router plain objects and then preloading only the essential async chunk files and performing data fetching. All of that occurs in parallel during the initial load.
Background
When using react router with code splitting (lazy loading) the process of loading a website is:
- Downloading and executing the initial HTML file.
- The HTML fetches and executes only the initial Javascripts and CSS chunks.
- The react router code examines the current URL path and runs
loader
method if defined. - The react router code examines the current URL path and fetching and executing only the relevant chunk files based on
element
property.
Each step involves a network request and happens sequentially. It is a waste of time! In this article we show a solution that 2nd, 3rd and 4th steps will be parallelized network requests, which significantly reducing loading times and improving user experience.
A regular initial webpage loading example
Before I show the solution I want to show through a basic example the network waterwall of the traditional react webpage like we described in the Background section.
In this example, the Products
component is code splitted through lazy loading.
//...
const Products = lazy(() =>
import(/* webpackChunkName: 'Products' */ "./Products")
);
//...
So Let's see in DevTools the network waterwall when we navigate to https://regular-81820.web.app/products.
We see that there are 4 network requests in a row and this we want to parallelize as much as possible.
The solution
After I read the great article by Almog Gabay on Client-Side Rendering (CSR), particularly the Preloading Async pages section, I tought to myself why not using react router plain objects for another purposes like preloading with help from Webpack? The idea was to have a single array of plain objects serving multiple purposes - routing and preloading. After experimenting with various edge cases, I successfully created a stable Webpack plugin. This plugin generates an HTML file that handles preloading of async chunk files derived from React Router’s plain objects and also preloads data from the database (this feature is currently limited to GET requests).
A new webpack plugin - html-webpack-preload-react-router
The plugin html-webpack-preload-react-router
is easy to install and setup. It gets as an argument the path for the entry route of the website.
In each object of react router plain objects we need to give the chunk name in a property called chunkName
. The value needs to be the same value of the magic comment webpackChunkName
of import()
.
In addition, because we make a fetching from database, we can preload also the data by configure the property data
.
It will be easier to understand throughout an example.
A preloading initial webpage loading example
Let's use the same website example of A regular initial webpage loading example.
And do the following steps:
-
Install the plugin.
npm install --save-dev html-webpack-preload-react-router
-
Configure the
webpack.config.js
file:const HtmlWebpackPlugin = require("html-webpack-plugin"); + const ReactRouterPreloadChunksPlugin = require("html-webpack-preload-react-router"); + const entryRouteFilename = path.resolve(`./src/pages/entryRoute.route.js`); module.exports = { //... plugins: [ new MiniCssExtractPlugin(), //... + new ReactRouterPreloadChunksPlugin({ + entryRoute: entryRouteFilename, + }), ] //... };
-
Add
chunkName
property for each object in the routing of react router plain objects.export const productsRoutes = [ { path: "", + chunkName: "Products", id: "products", loader: () => fetch("https://dummyjson.com/products").then((res) => res.json()), element: <Products />, children: [ { path: ":productid", + chunkName: "Product", element: <Product />, }, ], }, ];
-
Add
data
property for each object in the routing of react router plain objects that fetch a data.export const productsRoutes = [ { path: "", chunkName: "Products", id: "products", loader: () => fetch("https://dummyjson.com/products").then((res) => res.json()), element: <Products />, children: [ { path: ":productid", chunkName: "Product", element: <Product />, }, ], + data: { + url: "https://dummyjson.com/products", + crossorigin: "anonymous", + }, }, ];
So let's see in DevTools the network waterwall when navigating to https://preloading-ea687.web.app/products:
Now we can see in the DevTools that there are 2 network group requests (instead 4 in a row), each group has network requests in parallel (first is products
and second are main.9aa6dd.js
, Products.023320.js
and products
in parallel). This is because the HTML is using preloading for async chunk files according to the URL path. It improves our website's performance!
Bonus - Navigation bar
After we saw that we can use react router plain objects for 2 purposes of routing and preloading, I found a third purpose of using - navigation bar.
In each object of the react router plain objects we need to give the link text in a property called linkText
.
It will be easier to understand throughout an example.
A navigation bar example
Let's use the same website example of A preloading initial webpage loading example.
And do the following steps:
-
Change the react router plain objects
entryRoute
:export const entryRoute = [ { path: "", element: <Layout />, + id: "links", + loader: () => entryRoute[0].children, //... }, ];
-
Change the
Layout
component for reading link texts from the react router plain objectsentryRoute
://... function Layout() { + let links = useRouteLoaderData("links"); return ( <nav> <ul> - <li> - <Link to="/">Home</Link> - </li> - <li> - <Link to="/about">About</Link> - </li> - <li> - <Link to="/products">Products</Link> - </li> + {links.map((link) => { + return ( + <li> + <Link to={link.path?.replace("/*", "")}>{link.linkText}</Link> + </li> + ); + })} </ul> </nav> //... ); }
Now we can see that the navigation bar works the same but use the data from react router plain objects. It is easier to maintain a single source of truth rather than 3 different sources.
Summary
In this article, we tried a new webpack plugin html-webpack-preload-react-router
, which collaborates seamlessly with React Router’s plain objects. It generates a production html file that preloads async chunk files and fetched data based on the current URL path. It parallelizes network requests, which makes our webpage load faster! Additionally, React Router’s plain objects can be utilized for the navigation bar.
In short, now we can use the react router plain objects as a single source of truth for 3 purposes:
- Routing.
- Preloading async chunk files and fetched data from database (GET requests).
- Navigation.
It makes our website's code easy to understand and maintained and improve the performance of initial loading webpages in our website.
I hope you enjoyed to read my article and hope you will try my new Webpack plugin html-webpack-preload-react-router
.
Top comments (0)