Making WebAssembly Work in Next.js 16 with Turbopack
Starting with Next.js 16, Turbopack is now the default bundler. In theory, that’s great: it’s fast, modern, and designed for the future of the framework.
In practice, WebAssembly support remains limited. Simply importing a WebAssembly-based package can trigger cryptic bundling errors, even if the code path is never executed. Workflows that Webpack handled seamlessly now cause Turbopack to choke with little diagnostic guidance.
This tutorial presents a pragmatic, repeatable approach to using WebAssembly packages reliably in Next.js 16 with Turbopack.
Understanding the Root of the Problem
Most WebAssembly libraries published on npm are built using the popular Emscripten compiler. Internally, these packages usually contain code like this in their module factory:
return new URL("name.wasm", import.meta.url).href;
Turbopack currently does not understand how to resolve .wasm assets referenced this way. As a result, you’ll typically see errors such as:
Module not found: Can't resolve 'name.wasm'
The frustrating part is that this happens even if the relevant code path is never executed. A simple import is enough to break the build.
A commonly suggested workaround is to manually copy .wasm files into public/ and load them yourself.
This might work for trivial examples, but it quickly becomes impractical for complex packages that expect Emscripten’s runtime behavior.
export async function loadWasm(
url: string
): Promise<WebAssembly.WebAssemblyInstantiatedSource> {
return WebAssembly.instantiateStreaming(fetch(url), {...});
}
A Practical Solution
In order to integrate WebAssembly packages successfully, the goal is to ensure that Turbopack can resolve the .wasm asset at build time,
while also allowing the Emscripten module to load that same asset correctly at runtime.
Step 1: Help Turbopack Resolve the WebAssembly File
Turbopack currently requires explicit, statically analyzable paths for non-JavaScript assets.
The simplest way to satisfy this requirement is to define a manual alias for the .wasm file in next.config.ts.
const nextConfig: NextConfig = {
...
turbopack: {
resolveAlias: {
"name.wasm": "./node_modules/my_package/dist/name.wasm",
},
},
};
This ensures that the .wasm file can be resolved correctly during the bundling phase and prevents an immediate module resolution failure.
Step 2: Explicitly Bundle and Locate the WebAssembly File at Runtime
The alias alone isn’t enough.
Turbopack will not bundle the .wasm file unless it is explicitly referenced in application code, outside of the installed package.
Because Turbopack only bundles assets that appear directly in the module graph, this explicit reference both triggers proper bundling and ensures that a correct runtime URL is generated.
Fortunately, Emscripten-generated modules allow you to override the locateFile function.
This override can be passed when instantiating the module to point it to the bundled .wasm asset location.
import initModule from "my_package.js";
const moduleOverrides: Partial<EmscriptenModule> = {
locateFile() {
return new URL("name.wasm", import.meta.url).href;
},
};
(async () => {
const M = await initModule(moduleOverrides);
})();
Important constraints:
- The filename inside
new URL(…)must be static. No variables, no dynamic expressions. - The name must exactly match the original
.wasmfilename used by the package.
Summary
Until Turbopack gains full WebAssembly support, this approach provides a sane and repeatable way to keep using modern WebAssembly packages in Next.js 16 without falling back to Webpack.
Additional Common Workarounds
Another common Turbopack failure mode comes from code intended to run in both Node.js and the browser (Emscripten-generated packages are often compiled for multiple environments). You’ll sometimes see lines like this in the module factory:
await import("module");
Even if that branch is never executed, Turbopack still tries to resolve it and fails. The simplest workaround is to provide a dummy module that Turbopack can statically resolve.
export default {};
Then alias it in the config:
const nextConfig: NextConfig = {
...
turbopack: {
resolveAlias: {
...
"module": "./dummy.ts",
},
},
};
This is crude, but effective, and often enough to unblock the build.
Using WebAssembly Inside Web Workers
Web Workers require one extra adjustment.
Inside a Web Worker (WorkerGlobalScope), relative URLs behave differently, so you need to ensure the .wasm file resolves to an absolute URL.
const moduleOverrides: Partial<EmscriptenModule> = {
locateFile() {
return new URL(
new URL("name.wasm", import.meta.url).href,
self.location.origin,
).href;
},
};
With this change, the same approach works reliably inside workers as well.
Full Working Example
For a complete, real-world example, check out this proof of concept repository demonstrating how to integrate mediainfo.js into a Next.js application bundled with Turbopack. It shows all of the techniques described above working together in a minimal example:
https://github.com/rikublock/concept-nextjs-mediainfo