Single-page applications built with Vue.js often face performance challenges due to factors such as downloading and parsing large JavaScript bundles, reactive data binding, or the virtual DOM. Web workers offer an excellent solution for enhancing Vue app performance.
Web workers operate independently within their threads, distinct from the browser’s main thread. This separation lets web workers maintain functionality even when the main thread is occupied, ensuring your Vue app remains responsive during resource-intensive tasks.
In this article, we will explore how web workers can significantly improve the performance of Vue applications by offloading resource-intensive tasks to separate threads, thus enhancing responsiveness and overall user satisfaction.
Jump ahead:
You can check out the full code for our demo Vue app using web workers on GitHub.
JavaScript, by default, is single-threaded. This means there’s only one thread or single part, and this thread is known as the main thread. The main thread executes all the JavaScript for a webpage one line at a time.
A web worker is a separate JavaScript process that runs in the background of a web page and allows you to execute multiple threads of JavaScript in parallel with each other.
Instead of the main thread working by itself, you can use a web worker to offload any computationally expensive work. This means the main thread doesn’t get bogged down and can continue executing some other code.
One main difference between the main thread and the web worker thread is that web workers cannot manipulate the DOM. Only the main thread can access and manipulate the DOM.
This distinction is important to note as it promotes better architectural decisions, improves application performance, enhances security, and contributes to a more seamless and reliable user experience in web development.
Web workers have the potential to revolutionize Vue.js app optimization. They provide numerous advantages that lead to a better user experience, such as:
Now that we have a basic understanding of web workers, let’s move on to showcase a project utilizing web workers.
Let’s create a project that demonstrates how web workers work. Using Vue.js, we’ll create an application that fetches real-time cryptocurrency data, all while keeping the user interface responsive and smooth.
The web workers will operate independently from the main thread. This will help to offload resource-intensive tasks like data fetching to background threads. This ensures that our app remains fast and interactive, even when handling complex operations.
To follow along with this project, you must have:
Let’s get started.
Open up the terminal, navigate to the folder where you want to install the project, and run this command:
npm create vue@latest
You’ll be prompted to name your project. I’ll name mine vue-workers
, but you can name yours whatever you want. Follow the prompts and instructions when installing. When you’re done, change the directory to the just installed project and then run the following command:
npm install
This will install all the dependencies needed. Let’s do a little cleanup. Navigate to src/app.vue
and delete unnecessary code in our <template>
. It should now look like this:
<script> import HelloWorld from './components/HelloWorld.vue' import TheWelcome from './components/TheWelcome.vue' </script> <template> </template>
We’ll come back to the <template>
later.
We’ll be fetching cryptocurrency data from Coin Gecko’s Public API into our Vue app. This API shows the names of trending crypto coins, their logos, and their price.
Inside our src
folder, create a file called worker.js
and write a function that fetches the cryptocurrency data from the API, maps it, and sends it back to the main thread:
function fetchCoins() { fetch('https://api.coingecko.com/api/v3/search/trending') .then((res) => res.json()) .then((data) => { const coins = data.coins.map((coin) => ({ name: coin.item.name, price: coin.item.price_btc, logo: coin.item.large })) console.log(coins) // Post the result (coins data) back to the main thread self.postMessage(coins) }) .catch((err) => console.log(err)) }
In the code above, the fetchCoins
function fetches cryptocurrency data from an API, processes the data into a simplified format, and then sends the processed data back to the main thread for further use in the Vue component.
To ensure that the data remains up-to-date, we’ll use setInterval
to repeatedly call the fetchCoins()
function every five seconds. Add this right after the fetchCoins()
code:
// Fetch data every 5 seconds setInterval(fetchCoins, 5000)
This periodic fetching ensures that the Vue app receives continuous updates without user interaction.
Lastly, in our worker.js
file, we’ll set up an event listener in a web worker. When a message is received from the main thread, the function fetchCoins()
is invoked. Right on top of the other code in this file, add the following:
self.onmessage = function () { fetchCoins() }
So that’s it for our worker.js
file. Let’s move to the app.vue
file still in our src
folder. Inside the <script>
tag, import composition functions and lifecycle hooks along with our web worker file:
import { ref, onMounted, onBeforeUnmount } from 'vue'; import Worker from './worker?worker';
We will use the composition functions and lifecycle hooks to enhance and manage the behavior of components.
Let’s define our component’s behavior and setup. Paste the code below right after our imported web worker inside our <script>
:
export default { setup() { const coins = ref([]) return { coins } } }
Next, we need to define our fetch
function. Inside our setup()
function, after const coin
, paste this:
const fetchCoins = () => { fetch('https://api.coingecko.com/api/v3/search/trending') .then((res) => res.json()) .then((data) => { const mappedCoins = data.coins.map((coin) => ({ name: coin.item.name, symbol: coin.item.symbol, price: coin.item.price_btc, logo: coin.item.large })) coins.value = mappedCoins // Update the coins array }) .catch((err) => console.error(err)) }
This function is responsible for fetching cryptocurrency data from the API. It uses the fetch
function to make a GET request to the specified API endpoint. Once the data is retrieved, it is converted to JSON format using .json()
.
The fetched data is then mapped into a simplified format — an array of objects with name
, symbol
, price
, and logo
properties — and assigned to the coins.value
property.
We then need to initialize our web worker. Right after our fetchCoins
function, paste this:
const worker = new Worker() worker.onmessage = (e) => { coins.value = e.data }
Here, we create a new web worker using the Worker
constructor. The worker.onmessage
event listener is set up to handle messages sent from the web worker.
When the worker sends a message — which happens periodically, as seen in the web worker code — the event handler updates the coins.value
with the data received, keeping the Vue component’s data in sync with the web worker.
Almost done. Let’s paste this right after our web worker code:
onMounted(() => { fetchCoins() worker.postMessage('start') // Start the worker }) // Terminate the web worker when the component is unmounted onBeforeUnmount(() => { worker.terminate() })
The above code manages the behavior of a web worker, ensuring that the web worker fetches cryptocurrency data and starts its task when the component is mounted. It then terminates the web worker when the component is about to be unmounted.
Lastly, we’ll add this inside our <template>
:
<div> <h1>Trending</h1> <div class="container"> <div v-for="coin in coins" :key="coin.name"> <h2>{{ coin.name }}</h2> <p>{{ coin.symbol }}</p> <p>{{ coin.price }} BTC</p> <img :src="coin.logo" :alt="coin.name" /> </div> </div> </div>
Congratulations! You’ve successfully created a Vue.js app that utilizes web workers to fetch cryptocurrency data in the background, enhancing performance and responsiveness.
Checking the results in your browser, you should see the following:
With that, our project is done. You can check out the full code on GitHub.
Keep in mind that web workers are not yet available to use globally. Here’s a chart showing its availability as of this article’s publication:
Make sure to check the most updated information on CanIUse.
It’s a good practice to have a fallback mechanism just in case a user’s browser does not support web workers, like so:
// confirm if the browser supports web workers if (typeof Worker !== "undefined") { const worker = new Worker("worker.js"); // Set up web worker tasks and communication here } else { // Fallback mechanism for browsers without web worker support }
The fallback mechanism directs the app to go back to executing tasks on the main thread if the user’s browser does not support web workers. This ensures that the application continues to run as intended in such cases — although somewhat less responsively.
Alternatively, you can use libraries like worker-loader
, comlink
, and workerize-loader
to emulate web worker behavior in unsupported browsers.
In this article, we looked at what web workers are and also built a sample project to showcase how we can optimize our Vue apps with web workers.
Using web workers to optimize Vue.js apps enhances performance and responsiveness. You can improve UX by offloading CPU-intensive operations, assuring responsiveness even during heavy workloads, and managing memory consumption more efficiently by leveraging the capabilities of background threads.
Web workers provide a comprehensive toolbox to elevate your Vue.js project’s performance and UX. Whether you’re using them for image processing, file uploading, data fetching, animations, or calculations, leveraging web workers in your Vue app is a great performance optimization tool.
Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps, including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.
The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error and what state the application was in when an issue occurred.
Modernize how you debug your Vue apps — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
One Reply to "Optimizing Vue.js apps with web workers"
Great article !
I am confused why function fetchCoins() is defined in two places, one in worker and one in the main thread ?
Is it because the main thread function will be invoked only the first time upon app/component load, and every time the setInterval is called, it invokes the function from the worker ?