5

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?

  • How about using service workers to cache these routes? including api routes, with local first strategies? It would not help the first time, but subsequent loads would be faster. – Jay Surya Dec 20 '20 at 12:36
  • Unfortunately the first time is the most critical for us. 95% of traffic comes from new users consuming these links off social like FB and IG. – Augusto Samamé Barrientos Dec 20 '20 at 16:37
  • I feel SSR is the missing link to your performance issues. I notice you mention that you cannot use node.js ability to provide SSR to the react application but have you tried to use a Django SSR to react integration? Using a some sort of middle layer to transition between Django to the react application, may not be the cleanest solution but I think its worth a try without having to make any other serious changes to get better performance. Probably check out whatever comes up on google from searching "django react SSR" – imGreg Dec 23 '20 at 19:36

2 Answers2

1

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")
  ]
};
Felipe Malara
  • 1,796
  • 1
  • 7
  • 13
  • Thanks for your answer, and I'm leaning towards react-static. My issue is that I can get it to work fine for a simple, new project, using react-static structure. But my large React project is structured differently, uses React Router (v4) and uses window and document everywhere, so it won't even build. I will look into it some more and choose this answer if I can make it work. – Augusto Samamé Barrientos Dec 26 '20 at 16:13
0

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.

O_Z
  • 1,515
  • 9
  • 11
  • Interesting approach. Will the service worker start "working" before all JS is loaded? Remember that these "critical pages" are the point of entry into the website, so if service worker can only start serving static page version ONCE everything loads, then the problem would still exist. – Augusto Samamé Barrientos Mar 01 '21 at 17:36