worker$()

A Web Worker lets the browser run JavaScript away from the main UI thread. This is useful when a calculation is large enough that it could make clicks, typing, scrolling, or animations feel stuck.

worker$() is Qwik's way to run one of your functions in a worker. You write a normal function and wrap it with worker$().

import { component$, useSignal } from '@qwik.dev/core';
import { worker$ } from '@qwik.dev/core/worker';
 
const incrementInWorker = worker$((count: number) => count + 1);
 
export default component$(() => {
  const count = useSignal(0);
 
  return (
    <button
      onClick$={async () => {
        count.value = await incrementInWorker(count.value);
      }}
    >
      Count: {count.value}
    </button>
  );
});

worker$() returns a QRL function. Calling it always returns a promise, so you can use await, .then(), or any other promise flow that fits your code.

What to Put in a Worker

Use worker$() for work that is expensive enough to move out of the UI path:

  • parsing or formatting large data
  • compression, hashing, or encoding
  • image, text, or number processing
  • calculations that would otherwise make the page feel unresponsive

Small operations are usually better left inline. Sending data to a worker and getting a response back has a cost, so worker$() is most useful when the work itself is meaningfully heavier than that cost.

Passing Data

Pass data that Qwik can serialize to worker functions. This follows the same serialization boundary rules as other Qwik QRLs.

import { worker$ } from '@qwik.dev/core/worker';
 
const formatMessage = worker$((name: string, count: number) => {
  return `${name}:${count}`;
});
 
formatMessage('qwik', 2).then((result) => {
  console.log(result);
});

Return values should also be serializable by Qwik.

Events and Forms

Browser event objects and DOM nodes should not be sent to a worker. Read the data you need from the event first, then pass that data to the worker.

For form submissions, Qwik helps by converting a form SubmitEvent into FormData before it reaches the worker.

import { component$, useSignal } from '@qwik.dev/core';
import { worker$ } from '@qwik.dev/core/worker';
 
const parseValue = worker$((value: string) => Number(value));
 
export default component$(() => {
  const count = useSignal(0);
 
  return (
    <input
      onInput$={async (_, target) => {
        count.value = await parseValue(target.value);
      }}
    />
  );
});
import { component$, useSignal } from '@qwik.dev/core';
import { worker$ } from '@qwik.dev/core/worker';
 
const submitInWorker = worker$((data: FormData) => {
  return `${data.get('name')}:${data.get('count')}`;
});
 
export default component$(() => {
  const result = useSignal('pending');
 
  return (
    <form
      preventdefault:submit
      onSubmit$={async (event) => {
        result.value = await submitInWorker(event);
      }}
    >
      <input name="name" value="qwik" />
      <input name="count" value="2" />
      <button type="submit">Submit to worker</button>
      <p>{result.value}</p>
    </form>
  );
});

Setup

No manual new Worker() setup is needed when using the Qwik Vite plugin. Import worker$() from @qwik.dev/core/worker, define the worker function, and call it from your component, event handler, task, or other Qwik code.

Server-side Rendering

worker$() is mainly useful to think about as a browser Web Worker API. During SSR, Qwik can also offload worker functions when worker support is available. If workers are not available in the current runtime, Qwik invokes the QRL directly instead.

Things to Remember

  • Worker functions return promises.
  • Pass data, not DOM nodes.
  • Keep arguments and return values serializable.
  • Prefer worker$() for expensive work, not tiny calculations.
  • Avoid browser-only globals inside the worker function.

API Reference