This post is about my learning on performance techniques used to make Uber mobile web using React as fast as possible.
Motivation
It’s been a year since Flipkart Lite was launched and few months since Housing Go was launched and I was always fascinated with the idea of how mobile web is a future and I wanted to give a try.
First I needed an app on which I can implement the perf techniques, and Uber had just recently launched their app with new design and it looked promising so I decided to clone the app using React.
It took me some time to build the basic implementation of the app, I have used https://github.com/uber/react-map-gl for the map and used svg-overlay
to create a path from the source and destination along with the html-overlay.
Below is the gif of the app with basic interaction.
Now that I had the basic app to work on, I started to improve the performance of it.
I have used Chrome Lighthouse to check the performance of the web app in each stage.
initially load time looked like this.
Code Splitting — reduces load time from 19sec to 4sec
First thing I did was used webpack code splitting techniques to divide the app into various chunks based on the route and load what is needed for that particular route.
This I did by using the getComponent
api of react-router
where I require the component only when the route is requested.
{ require.ensure([], (require) => { cb(null, require('../components/Home').default); }, 'HomeView'); }}>
I also extracted the vendor code using CommonChunkPlugin in webpack.
{ 'entry': { 'app': './src/index.js', 'vendor': [ 'react', 'react-redux', 'redux', 'react-router', 'redux-thunk' ] }, 'output': { 'path': path.resolve(__dirname, './dist'), 'publicPath': '/', 'filename': 'static/js/[name].[hash].js', 'chunkFilename': 'static/js/[name].[hash].js' }, 'plugins': [ new webpack.optimize.CommonsChunkPlugin({ name: ['vendor'], minChunks: Infinity, filename: 'static/js/[name].[hash].js', }), } }
}
With this I have reduced the load time from 19secs to 4secs.
2. Server side rendering — reduces load time from 4sec to 921ms
I then implemented SSR by rendering the initial route on the server and passing on to the client.
I used Express for this on the backend and used match
api of the react-router
server.use((req, res)=> { match({ 'routes': routes, 'location': req.url }, (error, redirectLocation, renderProps) => { if (error) { res.status(500).send(error.message); } else if (redirectLocation) { res.redirect(302, redirectLocation.pathname + redirectLocation.search); } else if (renderProps) { // Create a new Redux store instance const store = configureStore(); // Render the component to a string const html = renderToString( ); const preloadedState = store.getState(); fs.readFile('./dist/index.html', 'utf8', function (err, file) { if (err) { return console.log(err); } let document = file.replace(/
Thanks to SSR, now the load time is 921ms.
3. Compressed static assets — reduces load time from 921ms to 546ms
Then I decided to compress all the static files, this I did this by using CompressionPlugin in the webpack
{ 'plugins': [ new CompressionPlugin({ test: /\.js$|\.css$|\.html$/ }) ] }
and express-static-gzip to serve the compressed file from the server which falls back to uncompressed if required file is not found.
server.use('/static', expressStaticGzip('./dist/static', { 'maxAge': 31536000, setHeaders: function(res, path, stat) { res.setHeader("Expires", new Date(Date.now() + 2592000000).toUTCString()); return res; } }));
yippee I saved almost 400ms. Good job, Narendra!
4. Caching — helped load time of repeat visits from ~500ms to ~300ms
Now that i had improved the performance of my web app from 19seconds to 546ms, I wanted to cache static assets so the repeat visits are much faster.
I did this by using sw-toolbox
for browsers which support service workers
toolbox.router.get('(.*).js', toolbox.fastest, { 'origin':/.herokuapp.com|localhost|maps.googleapis.com/, 'mode':'cors', 'cache': { 'name': `js-assets-${VERSION}`, 'maxEntries': 50, 'maxAgeSeconds': 2592e3 } });
and cache-control
headers for browsers which don’t support.
res.setHeader("Expires", new Date(Date.now() + 2592000000).toUTCString());
By doing this I improved the repeat visit by approximately 200ms.
5. Preload and then load
I have used
in the head
tag and used prefetch
for the browser which doesn’t support preload.
At the end of the body I load the application JS in a regular tag.
For more about preload and prefetch please visit https://css-tricks.com/prefetching-preloading-prebrowsing/
Finally tested with Google PageSpeed and this is the result
This has been a good learning for me, I know I can optimize more and will keep exploring. Performance improvement is an ongoing process this is just a benchmark to what I have achieved. Give a try with your app and let me know your story.
Github: https://github.com/narendrashetty/uber-mobile-web
Live Demo: https://uberweb.herokuapp.com/ or
https://uberweb-v2.herokuapp.com/
Please give a visit to the demo on your mobile browser and share your inputs with me. (Since I hosted it on heroku, it’s goes down when it has no visits. Don’t lose patience if it doesn’t load at first :P Such an irony).
Live Now
Irine
Live Now : Irine
_