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 launched and a few months since Housing Go launched, and I was always fascinated with the idea of how the mobile web is a future, and I wanted to give it a try.

First, I needed an app on which I can implement the perf techniques, and Uber had just recently launched its app with a new design, and it looked promising, so I decided to clone the app using React.

It took me some time to build the underlying implementation of the app. I used react map gl for the map and used SVG-overlay to create a path from the source and destination along with the HTML-overlay.

TO IMPROVE THE ABOVE STATS I HAVE USED THE FOLLOWING TECHNIQUES

1. Code Splitting — reduces load time from 19sec to 4sec
The first thing I use is a webpack code-splitting technique to divide the app into various chunks based on the route and load cf what is needed for that particular route.
I did this 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 it on to the client.

I used Express for this on the backend and used the 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(/<\/div>/,'${html}`);
                document = document.replace(/'preloadedState'/,
                  `'${JSON.stringify(preloadedState)}'`);
                   res.setHeader('Cache-Control', 'public, max-age=31536000');
                   res.setHeader("Expires", new Date(Date.now() + 2592000000).toUTCString());
                   res.send(document); });
             } else {
                res.status(404).send('Not found') }
             });
           });

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, 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 the required file is not found. server

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 that 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 that 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

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 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 it a try with your app and let me know your story.

Github: Uber mobile web
Live Demo: Uberweb or  Uberweb v2