symbol-sdk v3 は WASM に強く依存しており、Next.js + Turbopack 構成でそのまま使うと Can't resolve 'fs' エラーで動作しない。
next.config.ts に2点の設定を加えることで SSR・CSR・Cloudflare Workers すべてで動作するようになる。
環境
> node -v
v24.13.0
>npm -v
11.6.2
Next.js アプリ作成
最新のcreate-next-appからウィザード形式で作成します
mkdir your-next-proj
cd your-next-proj
npx create-next-app@latest .
オプション設定
# (任意)もしこれがでたら'y'→Enter
Need to install the following packages:
create-next-app@16.1.6
Ok to proceed? (y) y
# recommendのままEnter
? Would you like to use the recommended Next.js defaults? › - Use arrow-keys. Return to submit.
❯ Yes, use recommended defaults - TypeScript, ESLint, Tailwind CSS, App Router
No, reuse previous settings
No, customize settings
必要なパッケージのインストール
symbol-sdk v3とsymbol-crypto-wasm-webをインストールします
npm i symbol-sdk@^3 symbol-crypto-wasm-web
ここまでの各パッケージのバージョンの確認
"dependencies": {
"next": "16.2.6",
"react": "19.2.4",
"react-dom": "19.2.4",
"symbol-crypto-wasm-web": "^0.1.1",
"symbol-sdk": "^3.3.1"
}
サンプルページ
ページに適当にsdkを呼ぶ処理を記述します。
今回はSSR,CSR双方で動くことを確認したいので以下の3つを作ります。
-
app/page.tsx:ルート(リンクのみ) -
app/ssr/page.tsx:SSR用の検証ページ -
app/csr/page.tsx:CSR用の検証ページ
app/page.tsx
export default function Home() {
return (
<main className="min-h-screen p-8">
<h1 className="text-2xl font-semibold">Repro Index</h1>
<p className="mt-2 text-sm text-zinc-600">
Choose SSR or CSR pages to reproduce the Symbol SDK v3 behavior.
</p>
<ul className="mt-6 list-disc pl-6">
<li>
<a className="text-blue-600 underline" href="/ssr">
SSR page
</a>
</li>
<li>
<a className="text-blue-600 underline" href="/csr">
CSR page
</a>
</li>
</ul>
</main>
);
}
app/ssr/page.tsx
import { PrivateKey } from "symbol-sdk";
import { KeyPair, SymbolFacade } from "symbol-sdk/symbol";
export default function SsrPage() {
const facade = new SymbolFacade("testnet");
const privateKey = PrivateKey.random();
const account = facade.createAccount(privateKey);
const addressRaw = account.address.toString();
const keyPair = new KeyPair(privateKey);
const publicKeyHex = keyPair.publicKey.toString();
return (
<main className="min-h-screen p-8">
<h1 className="text-2xl font-semibold">SSR</h1>
<ul className="mt-6 list-disc pl-6">
<li>Address: {addressRaw}</li>
<li>Public Key: {publicKeyHex}</li>
</ul>
</main>
);
}
app/csr/page.tsx
"use client";
import { PrivateKey } from "symbol-sdk";
import { KeyPair, SymbolFacade } from "symbol-sdk/symbol";
export default function SsrPage() {
const facade = new SymbolFacade("testnet");
const privateKey = PrivateKey.random();
const account = facade.createAccount(privateKey);
const addressRaw = account.address.toString();
const keyPair = new KeyPair(privateKey);
const publicKeyHex = keyPair.publicKey.toString();
return (
<main className="min-h-screen p-8">
<h1 className="text-2xl font-semibold">SSR</h1>
<ul className="mt-6 list-disc pl-6">
<li>Address: {addressRaw}</li>
<li>Public Key: {publicKeyHex}</li>
</ul>
</main>
);
}
動作確認(コケます)
- 開発サーバ起動
npm run dev
-
http://localhost:3000/にアクセス - CSRまたはSSRのリンククリック
これだけだとそれぞれのページでただしくSDKを呼べずに以下のようになると思います。
エラーログ(テキスト)
Module not found: Can't resolve 'fs'
./node_modules/symbol-crypto-wasm-node/symbol_crypto_wasm.js (250:15)
Module not found: Can't resolve 'fs'
248 |
249 | const path = require('path').join(__dirname, 'symbol_crypto_wasm_bg.wasm');
> 250 | const bytes = require('fs').readFileSync(path);
| ^^^^^^^^^^^^^
251 |
252 | const wasmModule = new WebAssembly.Module(bytes);
253 | const wasmInstance = new WebAssembly.Instance(wasmModule, imports);
Import traces:
Client Component Browser:
./node_modules/symbol-crypto-wasm-node/symbol_crypto_wasm.js [Client Component Browser]
./node_modules/symbol-sdk/src/impl/ed25519_wasm.js [Client Component Browser]
./node_modules/symbol-sdk/src/impl/ed25519.js [Client Component Browser]
./node_modules/symbol-sdk/src/symbol/KeyPair.js [Client Component Browser]
./node_modules/symbol-sdk/src/symbol/index.js [Client Component Browser]
./app/csr/page.tsx [Client Component Browser]
./app/csr/page.tsx [Server Component]
Client Component SSR:
./node_modules/symbol-crypto-wasm-node/symbol_crypto_wasm.js [Client Component SSR]
./node_modules/symbol-sdk/src/impl/ed25519_wasm.js [Client Component SSR]
./node_modules/symbol-sdk/src/impl/ed25519.js [Client Component SSR]
./node_modules/symbol-sdk/src/symbol/KeyPair.js [Client Component SSR]
./node_modules/symbol-sdk/src/symbol/index.js [Client Component SSR]
./app/csr/page.tsx [Client Component SSR]
./app/csr/page.tsx [Server Component]
https://nextjs.org/docs/messages/module-not-found
next.config.ts(js)の修正
webpackの場合
以前のwebpackを使ってるNext.jsのバージョンでは以下のやり方で動作するようです。
※一時期devだけturbopack, 本番ビルドはwebpackな時代もあったと思うのですがそれは未検証
Turbopackの場合(今回)
上記と若干書き方が違います。
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
reactStrictMode: true,
/**
* bitcore 系は bundle すると versionGuard により二重ロード扱いで落ちるため external 化する。
* symbol-sdk は WASM alias を効かせる必要があるため external 化しない。
*/
serverExternalPackages: ["bitcore-lib", "bitcore-mnemonic"],
/**
* Cloudflare Workers では fs / __dirname 前提の Node 版 WASM loader が使えないため、
* ESM import で WASM を扱う Web 版に差し替える。
*/
turbopack: {
resolveAlias: {
"symbol-crypto-wasm-node": "symbol-crypto-wasm-web/symbol_crypto_wasm.js",
},
},
};
export default nextConfig;
※上記のように修正してもエラーが出る場合は一旦 npm run dev を止めて再実行してください。
本番ビルドでも確認
以下のコマンドで本番ビルド&起動したURLにアクセスしても動作することを確認します。
npm run build
npm run start
まとめ
symbol-crypto-wasm-node → symbol-crypto-wasm-web への alias 差し替えと、bitcore 系の external 化の2点が要点。
Webpack 構成とは書き方が異なるため、Turbopack 移行時は turbopack.resolveAlias を使うこと。
