Improve Responsiveness to User Interactions
Tags: webdev, javascript, github, opensource
Published: 2023-01-16 13:46:33

I will share with you a story of performance improvement at my site.

## Prerequisite

I must tell you about prerequisite like npm dependencies before introducing the case.

The website that I improved is [Instagram Hashtag Translator](https://instagram-hashtag-translator.com/en) .
It helps us to translate Instagram hashtags.

![Instagram Hashtag Translator](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1wf77wzgxd7fzo6mkcgf.gif)

The technical conditions are as follows.

- npm dependencies
  - React v18.2.0
  - Next.js v13.0.5
- Hosting
  - Vercel

I measured the time it took to process the JavaScript when clicking the translate button.

```javascript
const clickHandler = () => {
  const start = performance.now();
  // ...
  // ...processing
  // ...
  console.log(Math.ceil((performance.now() - start) * 100) / 100);
}
```

## Before Improvement

```javascript
const clickHandler = async () => {
  // Dynamic import
  const { default: sendGaEvent } = await import(
    '~/lib/analytics/sendGaEvent'
  );
  // ...
  // Send analytics data
  sendGaEvent(data);
  // ...
  // Dynamic import
  const { default: translate } = await import('~/infra/translate');
  // ...
}
```

There are notable 2 points.

1. Import modules when user clicked the button
2. Send analytics data

### 1. Import modules when user clicked the button

By using [dynamic import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) , we can reduces the code needed on the initial load of our application.

It likes good idea, but there also is problem.
Dynamic import gives users bad responsiveness because it is not until they clicked the button that browsers load modules.

### 2. Send analytics data

Is it important to send analytics data?
This answer is no.

We want to have JavaScript main thread process important tasks 
like translating words as much as possible.

## After Improvement

I improve the performance by using `idle-task` .

https://github.com/hiroki0525/idle-task

```javascript
import { setIdleTask, forceRunIdleTask } from 'idle-task';

// point 1
const importSendGaEventTaskId = setIdleTask(
  () => import('~/common/analytics/sendGaEvent')
);
const importTranslateTaskId = setIdleTask(() => import('~/infra/translate'));

const clickHandler = async () => {
  // point 1
  const { default: sendGaEvent } = await forceRunIdleTask(
    importSendGaEventTaskId
  );
  // ...
  // point 2
  setIdleTask(() => sendGaEvent(data));
  // ...
  // point 1
  const { default: translate } = await forceRunIdleTask(importTranslateTaskId);
  // ...
}
```

### point 1

**I prepare browsers to load modules during their idle periods** 。

```javascript
const importSendGaEventTaskId = setIdleTask(
  () => import('~/common/analytics/sendGaEvent')
);
const importTranslateTaskId = setIdleTask(() => import('~/infra/translate'));
```

[`setIdleTask`](https://github.com/hiroki0525/idle-task#setidletask) can register tasks which will be executed when browsers are idle.

```javascript
  const { default: sendGaEvent } = await forceRunIdleTask(
    importSendGaEventTaskId
  );
  const { default: translate } = await forceRunIdleTask(importTranslateTaskId);
  // ...
}
```

We can get the results by using [`forceRunIdleTask`](https://github.com/hiroki0525/idle-task#forcerunidletask) .

It will not always be idle if browsers are busy.
`forceRunIdleTask` gives us the result from cache when the task had been executed, otherwise `idle-task` run immediately.

### point 2

**Send analytics data during a browser's idle periods** 。

```javascript
setIdleTask(() => sendGaEvent(data));
```
As I have already told you, [`setIdleTask`](https://github.com/hiroki0525/idle-task#setidletask) help us to execute tasks when browsers are idle.

It seems to have solved all my problems!

## Result

|  | Before(ms) | After(ms) |
| ---- | ---- | ---- |
| 1st |  1110.8  |  1048.8  |
| 2nd |  1191.2  |  1068.9  |
| 3rd |  1070.4  |  1028.4  |
| 4th |  1084.8  |  1023.2  |
| 5th |  1099.4  |  1080.8  |
| average |  1111.3 |  1050.0  |

That's an improvement of about **5.5%** 🎉.
In fact, the main translation process itself takes about 1,000 ms , so if we consider the non-main process, the improvement is about **55%**.

In absolute terms, this is an improvement of **50 to 60 ms**, which may not seem like much numerically at first glance, but an improvement of **50 to 60 ms** is significant.

In light of the [RAIL model](https://web.dev/i18n/en/rail/), users will feel comfortable when the execution time is roughly 100 ms or less (50 ms is recommended as a guideline).

Considering this, **an improvement of 50 to 60 ms would be significant** .

## Conclusion

I improved the performance of [Instagram Hashtag Translator](https://instagram-hashtag-translator.com/en) by using `idle-task` .

https://github.com/hiroki0525/idle-task

If you found this helpful, please help someone else by sharing it or tagging someone in the comments!

Thanks!