Lazy Loading Routes in React

November 07, 2017 0 Comments

Lazy Loading Routes in React

 

 

As developers, when we build apps for users on the internet, it is very important to ensure that the app is served to the user in the fastest way possible.

When building React applications, it's very common to see the size of the app grow largely due to the number of dependencies in use. This happens when a section of the app (or route) might import a large number of components that are not necessary when it first loads. This can reduce the initial load time of the app.

So how do we approach this? How do we make sure that the app only loads what is needed thereby avoiding bloat in the code?

We can do that by utilizing Lazy Loading. Lazy loading is a great way to optimize your site, and it does that by splitting your code at logical breakpoints, and then loading it once the user has done something that requires, or will require, a new block of code. This speeds up the initial load of the application and lightens its overall weight as some blocks may never even be loaded.

In React, we can lazy load components and routes by code splitting using Webpack. By splitting our app into chunks we can load & evaluate only the code that is required for the page rendered.

With all of that being mentioned, let's go ahead and create a basic React app and demonstrate how we can lazy load routes.

Bootstrap a React app with create-react-app

We'll be using the create-react-app CLI to bootstrap a new React app. The CLI, which was built by Facebook helps developers by creating a structured React app that works out of the box; no build configuration is needed.

Install the create-react-app tool with this command:

npm install -g create-react-app

Once the installation process has been completed, you can now create a new React app by using the command create-react-app lazy-loading-routes.

This generates a new folder with all the files required to run the React app. You can now run any of the following commands:

npm start
npm run build
npm test

The npm start command runs the app in development mode, the npm run build command builds the app for production to the build folder, and the npm test command runs the test watcher in an interactive mode.

The basic idea of the React app we are building is to have routes/components that use one or two React plugins. Without code splitting, all the React code and plugins in use will be bundled into one JavaScript file, but with code splitting, only the component/plugin needed would be loaded.

This isn’t a concern early on when our app is quite small, but it becomes an issue down the road when the app becomes quite large.

Let's get back to building the app. The create-react-app CLI generates a working React app for us as mentioned above and that means we can starting building immediately.

First of all, let's set up the basic routes we'll be needing for the React app. For routing, we'll be using react-router. You can add the react-router package to the app by running npm install react-router-dom in the terminal.

Once the installation is complete, we can now begin to create the components that will serve as the routes. For this app, we'll be using four routes; Home, Maps, Blog, and a catch-all route that serves as a 404 page NotFound.

Navigate to the src folder inside the project directory and run the following commands:

mkdir Home Maps Blog NotFound

That will create a folder for the different components to be used. It's basically a way to compartmentalize the React app.

Before we create the individual components to be used, let's edit the App.js file and set up the basic routes. Open up the App.js file and edit with the following code:

// Import React and Component
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch, Link
} from 'react-router-dom' // Import the Home component to be used below
import Home from './Home/Home'
// Import the Maps component to be used below
import Maps from './Maps/Maps'
// Import the Blogs component to be used below
import Blog from './Blog/Blog'
// Import the NotFound component to be used below
import NotFound from './NotFound/NotFound' // Import CSS from App.css
import './App.css';
import createBrowserHistory from 'history/createBrowserHistory'; const history = createBrowserHistory();
class App extends Component { render () { return ( <Router history={history}> <div> <header className="header"> <nav className="navbar container"> <div className="navbar-brand"> <Link to="/"> <span className="navbar-item">Lazy Loading Routes</span> </Link> </div> <div className="navbar-end"> <Link to="/maps"> <span className="navbar-item">Maps</span> </Link> <Link to="/blog"> <span className="navbar-item">Blog</span> </Link> </div> </nav> </header> <section className="content"> <Switch> <Route exact path="/" component={Home} /> <Route path="/maps" component={Maps} /> <Route path="/blog" component={Blog} /> <Route path="*" component={NotFound} /> </Switch> </section> </div> </Router> ) }
} export default App;

In the code block above, we imported React and its Component module using ES6 import, and we also imported BrowserRouter, Route, Switch and Link from react-router. Inside the render() function, we first created a view which the user can use to navigate the different routes and then the <Switch> component holds the different routes and the components that responds to them.

The App.css file should be edited with the following code:

.header { background-color: #000; padding-top: 15px;
}
.navbar { background-color: #000;
}
.navbar-item { color: white !important;
}
.content { margin-top: 50px;
}

Let's continue with the individual components, navigate into the Home folder and create the following files: Home.js and Home.css. Open up the Home.js file and edit with the following code:

import React, { Component } from 'react'
import './Home.css'
import Button from '../NavButton/NavButton' class Home extends Component { render () { return ( <div className="container"> <section className="section"> <div className="container"> <h1 className="title">Lazy Loading</h1> <h2 className="subtitle"> A simple app to demonstrate how lazy loading routes in React works. </h2> <section className="bottom"> <Button name="Go to About page" link="/about" /> <Button name="Go to Blog page" link="/blog" /> </section> </div> </section> </div> ) }
} export default Home

In the code block above, we basically just created the view for the Home component. A Button component is used, although it is yet to be created in this tutorial, it takes a prop of name and link. We also imported styles from the Home.css file. Let's write the CSS for that file.

.subtitle { margin-top: 10px !important;
}
.bottom { margin-top: 300px;
}
.bottom a { margin-right: 10px;
}

Next up is the Maps route, The maps page simply shows a Google map of a location using the google-map-react React plugin. Navigate to the Maps folder and create the following files: Maps.js and Maps.css. Open up the Maps.js file and edit with the following code:

import React, { Component } from 'react'
import './Maps.css'
import GoogleMapReact from 'google-map-react'; const MapsView = ({ text }) => ( <div style={{ position: 'relative', color: 'white', background: 'red', height: 40, width: 60, top: -20, left: -30, textAlign: 'center', paddingTop: '5px' }}> {text} </div>
); class Maps extends Component { static defaultProps = { center: {lat: 6.5244, lng: 3.3792}, zoom: 11 }; render () { return ( <div className="container"> <p>This page is simply a page that shows a Google Map view of a location. Play around with the coordinates to get a different view</p> <div className="map-container"> <GoogleMapReact defaultCenter={this.props.center} defaultZoom={this.props.zoom} > <MapsView lat={6.5244} lng={3.3792} text={'Your Location'} /> </GoogleMapReact> </div> </div> ) }
} export default Maps

In the code block above, we first imported React, and it's Component module. The google-map-react plugin is also imported. The MapsView() function takes in a parameter of text and puts that text in a div.

The next thing is the ES6 class named Maps that extends the component module from react. Inside the Maps component, we set some default props value by using the defaultProps object and the render() function contains the view and the GoogleMapReact component. If you'd like to read more on the google-map-react plugin, you can do that here.

Let's write the CSS for the Maps.css file. Open up the file and type in the following:

.map-container { height: 400px;
}

The next component is the Blog component, which uses a React plugin called react-markdown to render markdown into pure React components. Navigate to the Blog folder and create the Blog.js file. Open up the Blog.js file and edit with the following code:

import React, { Component } from 'react'
import ReactMarkdown from 'react-markdown' class Blog extends Component { constructor(props) { super(props); this.state = { markdownSrc: [ '# Lazy Loading Routes with React\n\nWhy do we need to lazy load routes?.\n\n* Reduce code bloat\n* Avoid loading all components at the same time ', '\n* React app loads faster', '\n* Load only the component that is needed and preload the others\n', '\n## A quote\n\n<blockquote>\n A man who splits his code ', 'is a wise man.\n</blockquote>\n\n## How about some code?\n', '```js\nimport React, { Component } from \'react\';\nimport asyncComponent from \'./AsyncComponent\'', '\n\nimport {\n' + ' BrowserRouter as Router,\n' + ' Route,\n' + ' Switch,\n' + ' Link\n' + '} from \'react-router-dom\'\n```\n\n\n' ].join(''), htmlMode: 'raw' }; } render () { return ( <div className="container"> <ReactMarkdown source={this.state.markdownSrc} /> </div> ) }
} export default Blog

In the code block above, we imported the react-markdown plugin and used it to render the markdown in the state.markdownSrc into a pure React component in the render() function.

The last component is the catch-all route, the NotFound route, navigate into the NotFound folder and create the NotFound.js file. Open up the NotFound.js file and edit with the following code:

import React, { Component } from 'react' class NotFound extends Component { render () { return ( <div className="container"> <p>404</p> <p>Page not found - </p> </div> ) }
} export default NotFound

We used a Button component in the Home route above, let's create the component now. In the src folder, create a folder titled NavButton and then create a file named NavButton.js inside the newly created folder. Open up the NavButton.js file and edit with the following code:

import React from 'react';
import { Link } from 'react-router-dom' const button = { backgroundColor: 'white', padding: 10, color: 'black', borderRadius: 1, borderColor: 'black', borderWidth: 2, borderStyle: 'solid'
} const Button = (props) => { return ( <Link to={props.link}> <span style={button}>{props.name}</span> </Link> )
} export default Button

In the code block above, we created a functional stateless component that builds a view for a button. The component is a button that helps with navigation in the React app, and that is achieved by using react-router which is imported at the top of the file. The Button component takes in two props; link and name. The link prop is used to determine what route to navigate to, and the name prop is used to display a text within the button.

Right now, you can run the app to see the progress. To see the app in development mode, run the command npm start in your terminal and you should see a homepage similar to the one below.

Now that we know the app works fine, let's do some analysis and see how the app loads all the JavaScript code we've written. Run the command npm run build in your terminal to build the app for production.

As you can see from above, all the JavaScript code is bundled into one file main.....js with a relatively small size. This is quite okay but can prove problematic if the size of the JavaScript file becomes too large. Let's see how we can fix this with code splitting.

Code Splitting

The reason why we are here. How do we implement code splitting in a React app? This can be done by using Webpack and since create-react-app already ships with Webpack, there will be no need for extra config or ejecting create-react-app.

Let's take a look at the routes setup we defined up there:

import Home from './Home/Home'
import Maps from './Maps/Maps'
import Blog from './Blog/Blog' <Switch> <Route exact path="/" component={HomeComponent} /> <Route path="/maps" component={MapsComponent} /> <Route path="/blog" component={BlogComponent} /> <Route path="*" component={NotFound} />
</Switch>

With the current setup above, the Switch component renders the route that matches the path that users navigate to by using the components above. Because we import all the components at the top, this means that all components are loaded when a user goes to a particular route, even though the rest is not needed at all.

This is where code splitting comes in. Code splitting helps to dynamically import components and only load them whenever they are needed thereby removing unnecessary JavaScript code that's not needed. So how do we go about code splitting?

Create a file titled AsyncComponent.js in the src folder and edit with the following:

import React, { Component } from "react";
export default function asyncComponent(getComponent) { class AsyncComponent extends Component { static Component = null; state = { Component: AsyncComponent.Component }; componentWillMount() { if (!this.state.Component) { getComponent().then(Component => { AsyncComponent.Component = Component this.setState({ Component }) }) } } render() { const { Component } = this.state if (Component) { return <Component {...this.props} /> } return null } } return AsyncComponent;
}

The asyncComponent() function takes a parameter, getComponent which is a function that will dynamically import a given component. It will not be called until the first mount. On componentWillMount, we simply call the getComponent function that is passed in and save the dynamically loaded component in the state. Finally, we conditionally render the component if it has completed loading, if not we simply render null.

Now that we have the asyncComponent down, let's change how we import the components in the App.js file by importing them dynamically with the asyncComponent() function. The code block below in the App.js file should be replaced with the second code block.

import Home from './Home/Home'
import Maps from './Maps/Maps'
import Blog from './Blog/Blog'
// Dynamically imported components
const Home = asyncComponent(() => import('./Home/Home').then(module => module.default)
) const Maps = asyncComponent(() => import('./Maps/Maps').then(module => module.default)
) const Blog = asyncComponent(() => import('./Blog/Blog').then(module => module.default)
)

Your final App.js should look like the code block below:

import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch, Link
} from 'react-router-dom' import NotFound from './NotFound/NotFound'
import './App.css';
import createBrowserHistory from 'history/createBrowserHistory';
import asyncComponent from './AsyncComponent' const Home = asyncComponent(() => import('./Home/Home').then(module => module.default)
) const Maps = asyncComponent(() => import('./Maps/Maps').then(module => module.default)
) const Blog = asyncComponent(() => import('./Blog/Blog').then(module => module.default)
) const history = createBrowserHistory(); class App extends Component { render () { return ( <Router history={history}> <div> <header className="header"> <nav className="navbar container"> <div className="navbar-brand"> <Link to="/"> <span className="navbar-item">Lazy Loading Routes</span> </Link> </div> <div className="navbar-end"> <Link to="/maps"> <span className="navbar-item">Maps</span> </Link> <Link to="/blog"> <span className="navbar-item">Blog</span> </Link> </div> </nav> </header> <section className="content"> <Switch> <Route exact path="/" component={Home} /> <Route path="/maps" component={Maps} /> <Route path="/blog" component={Blog} /> <Route path="*" component={NotFound} /> </Switch> </section> </div> </Router> ) }
} export default App;

Now that we've implemented code splitting, let's do some analysis and see how the app bundles the JavaScript code now. Run the command npm run build in your terminal to build the app for production.

As you can see from above, our code is now being separated into different chunks, and the React will now load only the component needed for a path as opposed to loading everything. That's the power of code splitting.

Conclusion

In this tutorial, we saw how to avoid code bloat by implementing code splitting. We wrote code to dynamically import components and only load them when needed.

I should mention that if you'd want to avoid all this hassle, you can use the react-loadable plugin. It is a higher order component that takes care of loading components with promises.

If you'd like to see the complete React app and play around with it, you can check it out here.


Tag cloud