resources
|
November 17, 2024

Boosting Kickoff Performance: Enhancing Initial Load Time for Micro Front-End Pages in React Native (RN) Apps

Boosting Kickoff Performance: Enhancing Initial Load Time for Micro Front-End Pages in React Native (RN) Apps
Share article

Micro front-end pages are the backbone of our My11Circle and RummyCircle apps, accessed through various touchpoints. However, slow load times are currently a thorn in our users' side, diminishing their experience. By reducing load times and streamlining the loading process, we aim to elevate user experience and boost engagement with our app.

The React web applications currently used in our apps, such as the cash deposit page, bring-a-friend page, and cash withdrawal page, are vital across all Games24x7 products. These pages have a high load time, often delaying user interaction and causing drop-offs in user journeys.

Our Approach to Solve This Problem:

To improve the load time of the web view pages, we need to pre-cache the assets (mainly.js chunk files, which are huge).

Imagine you're a librarian preparing for a busy day. You know certain books are frequently requested, so instead of fetching them from the archive every time, you place them on a cart by your desk in the morning. This way, when a patron asks for a popular book, you can hand it to them instantly. Pre-caching assets works similarly; we prepare and store frequently needed files in advance, so when users request them, we can deliver them without delay.

We can pre-cache the assets by initiating a download manager in the Android layer and keeping a list of assets to be cached. This download manager acts like our diligent librarian, managing the download and cache purge logic.

Once assets are cached, whenever the user opens a web view, the Android layer will intercept the network call (using the shouldInterceptRequest method of WebView) and return the cached asset. Think of it as a smart traffic cop who directs cars (requests) to the faster lane (cached assets), ensuring smooth and quick delivery.

This will require tweaking the react-native-webview node module, which acts as a bridge for mounting WebView components in React Native. It's akin to upgrading our library cart's wheels to make it roll faster and more efficiently around the library.

By implementing this approach, we expect to significantly reduce the first load time, improving the overall user experience and keeping our users engaged and satisfied with our apps.

Key Ingredients to Achieve This:

  1. On-Demand Lambda Function:
    • Generates assets metadata JSON and stores it in S3.
  2. Android Layer:
    • Handles asset downloads.
  3. React Native:
    • Calls the API to get assets metadata and triggers downloads according to initiation points.
  4. React-Native-Webview Node Module:
    • Feeds downloaded assets to WebView.
  5. Configuration:
    • Includes all switches to control the caching mechanism.

Dive into Each Ingredient:

(1) Lambda Function: An on-demand Lambda function generates metadata JSON by consuming the React web page build’s manifest.json, which contains a list of assets to be downloaded. Versions of metadata.json will be maintained in configuration to avoid frequent downloads. Whenever a new web page build is generated, the metadata JSON version will be incremented with the updated assets list.

Sample metadata.json:

[
  {
    "initiationPoint": "on_screen1_load",
    "assets": [
      {
        "name": "micro_app_1",
        "assets": [
          {
            "name": "micro_app_1-<tag>",
            "assets": ["<asset-url>", "<asset-url>"]
          },
          {
            "name": "micro_app_11-<tag>",
            "assets": ["<asset-url>", "<asset-url>"]
          }
        ],
        "priority": 1
      },
      {
        "name": "withdraw",
        "assets": [
          {
            "name": "withdraw-B-<tag>",
            "assets": ["<asset-url>", "<asset-url>"]
          }
        ],
        "priority": 1
      }
    ]
  },
  {
    "initiationPoint": "load_of_lobby",
    "assets": [
      {
        "name": "baf",
        "assets": [
          {
            "name": "baf-Be-<tag>",
            "assets": ["<asset-url>", "<asset-url>"]
          }
        ],
        "priority": 2
      }
    ]
  }
]

(2 & 3) RN & Android - Assets Download Flow: On the trigger point (e.g., load of match lobby), initiate the caching logic. The downloading of the assets will be triggered only when there is a change in the assets list JSON.

Role of RN Layer:

  • Compare the metadata.json version with the current stored file version.
  • Download the latest metadata file.
  • Get metadata.json from CDN/S3 and pass the assets list to the Android layer.

Basic algo for getting latest metadata file:

function getAssetsMetaData(metadataVersion) {
    lastMetadataVersion = get metadataVersion from AsyncStorage;
    if (lastMetadataVersion !== metadataVersion) {
        metaData = download latest metada json file;
        store metaData json in AsyncStorage;
        store latest metadataVersion in AsyncStorage;
        return metaData;
    }
    metaData = get metaData json from AsyncStorage;
    return metaData;
}

  • Handling of download initiation points

Maintain a singleton instance of AssetsManager which will trigger assets download on initiation points.

Basic algorithm for sending assets list to Android download manager

class AssetsManager {
    metaData = null;
    // array will have initiationEvent key, to avoid multiple download
    triggers
    triggeredDownloads = [];
    // function to download the metadata file
    initialiseMetaDataJson() {
        metaData = getLegoAssetsMetaData();
    }
    // function to parse the list of asset url wrt intiation event
    initiateAssetDownload(initiationEvent) {
        // - check triggeredDownloads has the initiationEvent, then do not proceed for next steps
        // - extract assets list from metaData wrt initiationEvent
        // - call Android bridge fn to start download (Android layer will decide whether file exists or not)
        // - add initiationEvent to triggeredDownloads array
    }
}

Role of Android Layer:

  • Download assets.
  • Purge assets.

Here's the flow between the RN and Android layer:

Assets Download Management:

(4) Tweaking React-Native-Webview Node Module:

Create a patch for this module to feed cached assets whenever n/w call happens for the assets in WebView.

Feeding Cached assets to WebView using react-native-webview module:

Java

  • Add following props to WebView component
    • feedFromCache <boolean>: this will be used to decide whether to search files in the cache folder. This is required because all My11Circle web pages are using the same node module to load pages, if we don't add this check then unnecessary cache lookup logic will be executed.
    • cachedFolderPath <string[]>: this will be used to locate cached files, this shortens the time of file lookup logic. This array may have multiple folder paths to look for a file in cache. ex: ['lego/addcash-N-<tag>/', 'container/<tag>']
  • Add logic in
    • react-native-webview/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java file to feed file from cache and consume feedFromCache & cachedFolderPath props

Sample code:

@Override
public WebResourceResponse shouldInterceptRequest(WebView view,
  WebResourceRequest request) {
  if (this.feedFromCache == false) {
    return super.shouldInterceptRequest(view, request);
  }
  ReactContext reactContext = (ReactContext) view.getContext();
  Map < String, String > headers = new HashMap < String, String > ();
  headers.put("Access-Control-Allow-Origin", "*");
  // proceed for file lookup
  final String url = request.getUrl().toString();
  // get file name form the url
  // lookup in cached folder for the file
  // return the cached file as WebResourceResponse object as like below code
  try {
    InputStream chunk =
      reactContext.getAssets().open("index.html");
    WebResourceResponse file1 = new
    WebResourceResponse("text/html", "gzip", chunk2);
    file1.setResponseHeaders(headers);
    return file1;
  } catch (IOException exception) {
    Log.e("shouldInterceptRequest", "index.html Error " +
      exception.toString());
  }
}

(5) Configuration: Control switches

  • ignoreAppVersion<string[]> : Controls the full caching mechanism. Holds an array of My11Circle app versions which will not initiate or use the caching mechanism.
  • disableCaching<boolean>: Clears cache of all the cached assets. This flag is used to flush cache if something goes wrong in the caching mechanism.
  • ignoreCachingFor<array[string]>: Dictates which LEGO pages are not to be cached. This flag is consumed to derive the value for the new prop feedFromCache of react-native-webview.

Sample config:

{
  "cacheAssets": {
    "ignoreAppVersion": ["101.70"],
    "disableCaching": false,
    "ignoreCachingFor": ["addcash"]
  }
}

Conclusion:

By following this approach, we anticipate a significant improvement in the first load time of our micro front-end pages, leading to a better overall user experience and higher engagement rates.

However this improvement of page load time will vary upon vast parameters such as device internet strength, processor speed and RAM etc.

Here are the improvement metrics tested with 3 trials in 3G network:

Here's the chart comparing the load times before and after the optimization for each case. The clustered bars clearly show the reduction in "Fresh Load" times across the three cases, with a noticeable 30-40% reduction. The "Subsequent Load" times also show minor improvements.

References:

Author:

Basavaraj Kumbar is a senior developer specializing in crafting innovative solutions for complex challenges. With a keen instinct for enhancing the performance of existing systems, Basavaraj’s technical expertise encompasses but is not limited to, React Native, React, and Node.js. Leveraging this expertise and a collaborative approach, Basavaraj has successfully delivered numerous features for our Fantasy Sports platform.