NextJS, WebAssembly, and Web Workers

Tom Lagier
Studio Lagier
Published in
3 min readJun 20, 2021

--

I recently had to do some work to get WebAssembly and Web Workers working with NextJS 11, Typescript, and Webpack 5. I figured I’d share my results in case anyone else wants to get this set up.

If you’re just looking for the code, here’s an example repo.

Why this stack?

I’m building a game. I’m probably (definitely) over-engineering a bit (a lot?), but I want my main thread to be as clean as possible while I do the heavy lifting in workers. I also really want to mess around with WebAssembly, and I’m really interested in the interaction between wasm and rendering engines.

I chose the following stack to make this happen:

  • NextJS 11, wrapping Webpack 5, to handle my UI, server, and be my host application. This gives me an easy, out-of-the-box React application, simple deployments, and I can opt in to using it as an Express server when I need a backend.
  • Rendering with ThreeJS in a Web Worker using OffscreenCanvas. I couldn’t convince myself to use Rust-based rendering solutions, and ThreeJS gives me the toolkit to easily build as simple or complex a rendering engine as I need.
  • Game logic (physics, world updates) in Rust, compiled to WebAssembly using wasm-pack, and run in a worker. My game will include a fairly sophisticated simulation of plant growth, and I think writing this in Rust will be a great experiment and likely provide better performance.

Does it work?

Yep. It took a bit of work to get things going with Next & Webpack 5, but overall it’s not too bad. There is one outstanding Next bug that you need to monkey-patch but otherwise things work mostly out of the box.

Here’s a list of steps to add support for each technology.

  1. Start with a Typescript NextJS 11 app: npx create-next-app --ts
  2. Add your Rust crate: wasm-pack new rust . This will create a subproject in your Next app called rust.
  3. Add wasm-pack-plugin. Add its configuration to next.config.js under the webpack key.
  4. Adjust the webpack key in next.config.js to support WebAssembly by setting output.publicPath, experiments.syncWebAssembly, and adding a module rule for .wasm files.
  5. You’ll need to patch the NextJsSSRImport plugin to address this bug. Your final next.config.js should look like this.
  6. Create your workers in the src directory. Make sure to name them *.worker.ts.
  7. Add the TypeScript module definition for web worker modules to typings.d.ts or similar. Update your tsconfig.json to include the module definition if necessary.
  8. Import your WASM in your worker: const { my_rust_fn } = await import('../rust/pkg');
  9. Import your worker in whichever page it’s needed: const wasmWorker = new Worker(new URL('../src/wasm.worker.ts', import.meta.url));

See the code for a full example.

That’s it! You’ll get your Rust compiled to WebAssembly when you run next dev or next build, and you can use it in a worker without error. You’ll have the correct TS types within the worker and you can make your app just as fast as it can be.

--

--

Tom Lagier
Studio Lagier

Tech enthusiast in Los Osos — graphics in the front-end is what I like to work on. Founder @StudioLagier