Best way to speed up a big React project by using "Snapshots" of critical routes (static renders)

402 Views Asked by At

We have a large and complex traditional React app that we've been building for the last couple of years. It loads an index.html injects javascript and gets data from an API as is usual. Unfortunately, cold load times are pretty bad (5 - 7 seconds on average). Once everything loads, it's snappy as usual but the cold load times are killing us in specific "critical" pages. These are our public user pages, in the format of: https://mywebsite/userId

We're looking for a way to dramatically speed up loading times for these routes, with methods that go beyond code-splitting or resource optimization. We already do those, and are serving our app off a CDN.

We've looked at creating static "snapshots" of these user pages, that we need to load very fast using something like react-static, and serving them as static versions and hydrating them later. Rewriting our project using something like next.js or gatsby is not an option as it would entail too much work. SSR is also not an option as our entire backend is coded in Django rather than Node.js

Are we on the right track? Is it possible / worth it to use react-static (or a similar tool) to do this? There is a LOT of documentation on how to create react-static projects from scratch but nothing on how to convert an existing project over, even if it's just a small subset of routes like we need.

Also, once the data changes on our user pages, how do we trigger a "rebuild" of the appropriate snapshot? Users don't update their data that often, about 3 of 4 times per month, but we have 3K users, so maybe 15 updates per hour would be the average. Can we trigger only a rebuild of the routes that actually changed?

2

There are 2 best solutions below

1
On

You can use a Service Worker. Load the important fast pages as static, then in the background, using the Service worker load the longer resources.

You can also use a Service Worker for smart caching. For example, the server can set a cookie with the current resource version (comes with that first page), and the Service worker can compare this to it’s resource version , and decide whether to loat it from cache or go to the server.

1
On

Like you said, you could use react-static.
They have a feature which fills exactly with your need ( user's specific pages ).

In their example they use an array of posts to generate a specific static file for each of them.
This have a huge lesser amount of time taken to load, as it's only html static files.

Imagine having this scenario:

[
  {
    id: 'foo',
    ...
  },   
  {
    id: 'bar',
    ...
  },
  ...
]

Following the example below this would generate something like this ( at runtime ):

- src
  - pages
    - blog
      - posts
        - foo // Specific post page
        - bar // Specific post page

Look at into the example:

//static.config.js
export default {

  // resolves an array of route objects 
  getRoutes: async () => {

    // this is where you can make requests for data that will be needed for all
    // routes or multiple routes - values returned can then be reused in route objects below

    // ATTENTION: In here, instead of posts you'd fetch your users json data
    const { data: posts } = await axios.get(
      "https://jsonplaceholder.typicode.com/posts"
    );

    return [
      // route object
      {
        // React Static looks for files in src/pages (see plugins below) and matches them to path
        path: "/blog",
        // function that returns data for this specific route
        getData: () => ({
          posts
        }),
        // an array of children routes
        // in this case we are mapping through the blog posts from the post variable above
        // and setting a custom route for each one based off their post id
        children: posts.map(post => ({
          path: `/post/${post.id}`,
          // location of template for child route
          template: "src/containers/Post",
          // passing the individual post data needed
          getData: () => ({
            post
          })
        }))
      },
    ];
  },
  // basic template default plugins
  plugins: [
    [
      require.resolve("react-static-plugin-source-filesystem"),
      {
        location: path.resolve("./src/pages")
      }
    ],
    require.resolve("react-static-plugin-reach-router"),
    require.resolve("react-static-plugin-sitemap")
  ]
};