Leveraging Astro for React App Performance Boost

Earlier this week, I was working on optimizing an internal analysis website that uses many JSON files for its content collections. During the local development and with smaller datasets, the website was super fast, but when I received a larger dataset, I noticed that the website got very slow.

The slowness mainly came from the amount of data and the processing in React. The HTML page sizes quickly grew over 5 MB. The reason was that I retrieved the whole collection in the Astro component and passed it to the React component.

For the website I was working with Astro for building the pages, but still heavily relied on React for the data processing, searching, filtering, and more. The code for the simplified component looked like this:

1
2
3
4
5
6
7
---
import { getCollection } from "astro:content";

let dataSets = await getCollection("dataset")
---

<Dashboard data={dataSets} client:only />

To improve the performance, I had to understand Astro better and look into dynamic routing to improve the performance and lower the page sizes.

Optimizing the dataset size per page

First, I started thinking about how I could bring more logic over to Astro. Not every dashboard needs to have all the data always available. It could easily be split up into smaller datasets.

One way to achieve these smaller datasets, is to use Astro’s routing mechanism.

The idea was to create the following routes:

  • /dataset/: show an overview of all the available datasets
  • /dataset/<name>: show the dashboard with the processed dataset by its name

To get both routes, you must use the rest parameter in the filename. In my case, this looks like this: /pages/dataset/[...name].astro.

When using routing in Astro, you must define all the paths in the getStaticPaths() function on the page.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
---
import { getCollection } from "astro:content";

export function getStaticPaths() {
  return [
    { params: { name: undefined } },
    { params: { name: "a" } },
    { params: { name: "b" } },
  ];
}

// Retrieve the dataset its name
const { name } = Astro.params;
---

<h1>Dataset: {name || "overview"}</h1>
important

The params property is required to define the parameters of your paths. In my case, this is the name for the dataset and corresponds to the filename.

In the above code block, you can see a parameter name with the value set to undefined. This value is required to get the top-level page route.

In my case, datasets are not predefined, so I needed a dynamic routing approach which I achieved as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
---
import { getCollection } from "astro:content";

export function getStaticPaths() {
  let dataSets = await getCollection("dataset").map(ds => ds.name)

  // The logic of processing the dataset its data is removed for simplicity

  return [
    { params: { name: undefined } },
    ...dataSet.map(ds => ({ params: { name: ds.name } }))
  ];
}

// Retrieve the dataset its name
const { name } = Astro.params;
---

<h1>Dataset: {name || "overview"}</h1>

With the above code, I could get routes like:

  • /dataset/a
  • /dataset/b

Although there are pages for each dataset, there is still no data passed for each page, only the name of the dataset.

info

I tried to get the collection data outside the getStaticPaths() function and use it on the page and inside the routes, but this is impossible, as stated in the documentation - getStaticPaths.

Passing data to each route

To pass data to each of your routes, you need to define the props parameter on each route.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
---
import { getCollection } from "astro:content";

export function getStaticPaths() {
  let dataSets = await getCollection("dataset").map(ds => ds.name)

  // The logic of processing the dataset its data is removed for simplicity

  return [
    { params: { name: undefined }, props: { dataset: undefined } },
    ...dataSet.map(ds => ({ params: { name: ds.name }, props: { dataset: ds.data } }))
  ];
}

// Retrieve the dataset its name
const { name } = Astro.params;
const { dataset } = Astro.props;
---

<h1>Dataset: {name || "overview"}</h1>

{dataset && <Dashboard dataset={dataset} client:only />}

To retrieve the data for the dataset, I only needed to add the following line: const { dataSet } = Astro.props;.

info

The Dashboard component is simplified, as the dataset analysis is moved into the Astro component.

With this logic in place, the original dataset page got split into an x-number of pages (depending on the number of JSON files and data to analyze), which are now much smaller and more performant as they only contain the data required to render the page.

Comments

Back to top