code splitting
Dynamic imports with meteor and react-router

Jan 2018

If you're using react-router (V4) with meteor and have screens in your app that aren't needed by all of your website visitors, then you can reduce the bundle size, for those visitors, with dynamic imports.

By using dynamic imports, with react-router, I've reduced my app's bundle size by 120kB, for 99% of visitors. To find out how - and see an example - read on!


I'm building an online booking system for barbers. 99% of the site's visitors are people wanting to book a haircut; they only need a few screens to do this. The final 1% are the barbers, who require many more screens/modules to manage their account, services, timetables etc.

Rather than declaring (and hence importing) all of our Routes upfront, we instead only decalre what Routes the user needs. We determine if the visitor needs the staff routes by using the alanning/roles package, to see if they are staff. If they are staff, then the extra Routes are asynchronously loaded.

Below is a simplified version of my code-split app:

import _ from 'lodash';
import React from 'react';
import { Roles } from 'meteor/alanning:roles';
import { Route, Switch } from 'react-router-dom';
import Login from '../../screens/Login/Login';
import Home from '../../screens/Home/Home';
import Booking from '../../screens/Booking/Booking';
const staffRoles = ['admin', 'super-admin', 'staff'];
class App extends React.PureComponent {
constructor(props) {
super(props);
this.state = { routes: this.clientRoutes() };
this.buildRoutes = this.buildRoutes.bind(this);
}
componentDidMount() {
this.buildRoutes(this.props);
}
componentWillReceiveProps(nextProps) {
this.buildRoutes(nextProps);
}
clientRoutes() {
return [
<Route exact path="/" component={Home} key="c1"/>,
<Route exact path="/booking" component={Booking} key="c2"/>,
<Route path="/admin" component={Login} key="c3" />,
];
}
async buildRoutes(props) {
/*
// Here we dynamically import the staff routes
// This means that customers don't need to download any of the modules required for staff pages.
*/
const { user } = props;
if (_.isEmpty(user) || !Roles.userIsInRole(user._id, staffRoles, props.organisation.securityGroup)) {
this.setState({ routes: this.clientRoutes() });
} else {
const StaffRoutes = await import('./StaffRoutes');
const staffRoutes = StaffRoutes.Generate(props);
this.setState({ routes: [...staffRoutes, ...this.clientRoutes()] });
}
}
render() {
const { routes } = this.state;
return (
<Switch>
{routes}
</Switch>
);
}
}
App.propTypes = ({
user: PropTypes.object,
});
App.defaultProps = ({
user: null,
});
export default App;
view raw App.js hosted with ❤ by GitHub
import React from 'react';
import { Route } from 'react-router-dom';
import InitialSetup from '../../screens/InitialSetup/InitialSetup';
import Staff from '../../screens/Staff/Staff';
export const Generate = (props) => {
return [
<Route path="/admin/initial-setup" component={InitialSetup} {...props} key="s1" />,
<Route path="/admin/staff" component={Staff} {...props} key="s2" />,
];
};
view raw StaffRoutes.js hosted with ❤ by GitHub