如何在Next.js中导入Wasm?已配置asyncWebAssembly仍报错
问题背景
相同的WebAssembly导入逻辑在纯React项目中可正常运行,但迁移到Next.js后出现webpack解析错误,即便已在next.config.js中开启experiments.asyncWebAssembly配置,问题依然存在。
报错信息如下:
./public/js/gameplay.wasm
Module parse failed: Unexpected character '' (1:0)
The module seem to be a WebAssembly module, but module is not flagged as WebAssembly module for webpack.
BREAKING CHANGE: Since webpack 5 WebAssembly is not enabled by default and flagged as experimental feature.
You need to enable one of the WebAssembly experiments via 'experiments.asyncWebAssembly: true' (based on async modules) or 'experiments.syncWebAssembly: true' (like webpack 4, deprecated).
For files that transpile to WebAssembly, make sure to set the module type in the 'module.rules' section of the config (e. g. 'type: "webassembly/async"').
(Source code omitted for this binary file)
解决方案
方案1:调整webpack配置+动态导入
Next.js的webpack默认不会自动识别.wasm文件,需手动添加解析规则,同时确保Wasm加载逻辑仅在客户端执行(避免服务端渲染报错)。
- 修改
next.config.js:
/** @type {import('next').NextConfig} */ const nextConfig = { webpack: (config) => { config.experiments = { asyncWebAssembly: true, layers: true, }; // 添加Wasm文件解析规则 config.module.rules.push({ test: /\.wasm$/, type: 'webassembly/async', }); return config; }, }; module.exports = nextConfig;
- 页面中使用动态导入:
将Wasm文件放在src目录下(而非public),在客户端useEffect中异步加载:
import { useEffect, useState } from 'react'; export default function Home() { const [greetMsg, setGreetMsg] = useState<string | null>(null); useEffect(() => { const loadWasm = async () => { try { // 替换为你的Wasm文件实际路径 const wasmModule = await import('../path/to/gameplay.wasm'); setGreetMsg(wasmModule.greet()); } catch (err) { console.error('Wasm加载失败:', err); } }; loadWasm(); }, []); return ( <div> <h1>Next.js + Wasm</h1> {greetMsg && <p>{greetMsg}</p>} </div> ); }
方案2:通过Fetch加载public目录中的Wasm
如果Wasm文件必须放在public目录下,可直接通过Fetch获取资源并实例化,绕开webpack的解析流程:
import { useEffect, useState } from 'react'; export default function Home() { const [greetMsg, setGreetMsg] = useState<string | null>(null); useEffect(() => { const loadWasm = async () => { try { const response = await fetch('/js/gameplay.wasm'); const bytes = await response.arrayBuffer(); const { instance } = await WebAssembly.instantiate(bytes); // 根据你的Wasm实际导出函数调整 setGreetMsg((instance.exports.greet as Function)()); } catch (err) { console.error('Wasm加载失败:', err); } }; loadWasm(); }, []); return ( <div> <h1>Next.js + Wasm</h1> {greetMsg && <p>{greetMsg}</p>} </div> ); }
方案3:使用wasm-bindgen生成绑定(推荐)
若你的Wasm由Rust编译,建议使用wasm-bindgen生成JS绑定文件,简化导入流程:
- 确保Rust编译时生成了绑定文件(通常在
pkg目录下) - 页面中导入绑定文件:
import { useEffect, useState } from 'react'; export default function Home() { const [greetMsg, setGreetMsg] = useState<string | null>(null); useEffect(() => { const loadWasm = async () => { const wasm = await import('../rust-wasm/pkg/rust_wasm'); setGreetMsg(wasm.greet()); }; loadWasm(); }, []); return <div>{greetMsg}</div>; }
此方案同样需要在next.config.js中开启asyncWebAssembly配置。
核心原因
Next.js存在服务端渲染(SSR)环境,Wasm模块仅能在浏览器客户端运行,因此必须将加载逻辑限制在客户端执行。同时,Next.js默认webpack配置未包含.wasm文件的解析规则,需手动添加或改用Fetch绕开webpack处理。
内容的提问来源于stack exchange,提问作者Justin Zhang

