iTranslated by AI
Easily Calling Web Workers in Next.js
How to Easily Use Web Workers in Next.js
As I mentioned in my previous article, Next.js includes Webpack configurations for loading Web Workers out of the box, so they can be used as is.
However, it only provides the configuration to load scripts written as Web Workers, and there is no other support.
To use the functionality, you need to write a somewhat cumbersome procedure in your code.
Previous Article
Calling Web Workers in Next.js
Sample Repository
https://github.com/SoraKumo001/next-worker2
<https://next-worker2.vercel.app/ >

Cumbersome Points of Using Web Workers
- You need to create a separate file for the Worker functionality.
While there is an inline feature, it is also cumbersome in its own way. - Communication between the main thread and the Worker must go through
postMessageandaddEventListener.
Even for simple data exchanges, this procedure makes the code less readable. - When trying to add TypeScript types for data exchange, you have to write them on both the Worker side and the main thread side.
I want to avoid the hassle of writing similar code twice.
Exploring a Solution
Just eliminate postMessage and addEventListener, and allow functions created on the Worker side to be called directly from the main thread.
What a great idea!
The problem was easily solved.
What I Created
I have registered a library on npm to make it easy to use Web Workers.
https://www.npmjs.com/package/worker-lib
Sample Code
Worker Side Processing
Create five functions and use initWorker to make them available as a Worker. Then, export only the type information.
- src/libs/worker-test.ts
import { initWorker } from "worker-lib";
const add = (a: number, b: number) => {
for (let i = 0; i < 1000000000; i++); //Overload unnecessarily
return a + b
}
const add2 = (a: string, b: string) => {
for (let i = 0; i < 1000000000; i++); //Overload unnecessarily
return a + b
}
const sub = (a: number, b: number) => {
for (let i = 0; i < 1000000000; i++); //Overload unnecessarily
return a - b
}
const mul = (a: number, b: number) => {
for (let i = 0; i < 1000000000; i++); //Overload unnecessarily
return a * b
}
const error = (a: number, b: number) => {
for (let i = 0; i < 1000000000; i++); //Overload unnecessarily
throw new Error("throw")
return a + b;
}
// Initialization process to make it usable in Worker.
const map = initWorker({ add, add2, sub, mul, error })
// Export only the type
export type WorkerTest = typeof map
Processing for the User (Next.js)
Load worker-test using createWorker. At this time, pass the type information that was exported under the name WorkerTest. Additionally, if you set the maximum number of threads here, calls exceeding the limit will be pooled and wait until execution becomes possible.
A function for executing the Worker is returned as the result, which you can then use to call the Worker.
- src/pages/index.tsx
import { useState } from "react";
import { createWorker } from "worker-lib";
import type { WorkerTest } from "../libs/worker-test";
// Create an instance to execute the Worker
// execute("function name",... parameter) to start the Worker
const execute = createWorker<WorkerTest>(
() => new Worker(new URL("../libs/worker-test", import.meta.url)),
5 // Maximum parallel number
);
const Page = () => {
const [values, setValues] = useState<(number | string)[]>([]);
const [a, setA] = useState(300);
const [b, setB] = useState(100);
return (
<div>
<form>
<input name="a" value={a} onChange={(e) => setA(Number(e.currentTarget.value))} />
<input name="b" value={b} onChange={(e) => setB(Number(e.currentTarget.value))} />
<button
type="button"
onClick={async () => {
const index = values.length;
setValues([...values, "running"]);
//Calling a Worker
const result = await execute("add", a, b);
setValues((values) => values.map((v, i) => (i === index ? result : v)));
}}
>
Add
</button>
<button
type="button"
onClick={async () => {
const index = values.length;
setValues([...values, "running"]);
//Calling a Worker
const result = await execute("add2", String(a), String(b));
setValues((values) => values.map((v, i) => (i === index ? result : v)));
}}
>
Add(String)
</button>
<button
type="button"
onClick={async () => {
const index = values.length;
setValues([...values, "running"]);
//Calling a Worker
const result = await execute("sub", a, b);
setValues((values) => values.map((v, i) => (i === index ? result : v)));
}}
>
Sub
</button>
<button
type="button"
onClick={async () => {
const index = values.length;
setValues([...values, "running"]);
//Calling a Worker
const result = await execute("mul", a, b);
setValues((values) => values.map((v, i) => (i === index ? result : v)));
}}
>
Mul
</button>
<button
type="button"
onClick={async () => {
const index = values.length;
setValues([...values, "running"]);
//Calling a Worker
const result = await execute("error", a, b).catch((e) => e);
setValues((values) => values.map((v, i) => (i === index ? result : v)));
}}
>
Error
</button>
</form>
{values.map((v, index) => (
<div key={index}>{v}</div>
))}
</div>
);
};
export default Page;
const result = await execute("add", a, b);
You use it in the form shown above. Since the type information corresponding to the name is automatically assigned, an error will occur if the parameter types are incorrect. Type information is also automatically set for the return value.
Conclusion
Since creating and using Worker functions has become intuitive, the barrier to entry has likely been lowered significantly. However, the reality is that most processing in web systems can be handled asynchronously, so finding a use case for multi-threading might be the more challenging part.
Discussion