環境
> 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.1.5",
"react": "19.2.3",
"react-dom": "19.2.3",
"symbol-crypto-wasm-web": "^0.1.1",
"symbol-sdk": "^3.3.0"
}
サンプルページ
ページに適当に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の場合(今回)
上記と若干書き方が違います。
next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
reactStrictMode: true,
/**
* symbol-sdk は Node.js 環境でのみ実行したい処理を含むため、
* Server Component 側では external として扱う。
*
* これを指定しないと Turbopack がクライアント向けに
* 無理にバンドルしようとしてエラーになることがある。
*/
serverExternalPackages: ["bitcore-lib", "bitcore-mnemonic", "symbol-sdk"],
/**
* Turbopack 用の alias 設定
*
* symbol-sdk は内部で `symbol-crypto-wasm-node` を参照するが、
* ブラウザ環境では `fs` が使えないためビルドエラーになる。
*
* ここで WebAssembly(Web) 版の実装に差し替える。
*/
turbopack: {
resolveAlias: {
"symbol-crypto-wasm-node": "symbol-crypto-wasm-web/symbol_crypto_wasm.js",
},
},
/**
* webpack 設定
*
* Turbopack 利用時でも、
* - 本番ビルド
* - 一部の依存解決
* では webpack が使われるケースがあるため、
* 同じ置き換えを webpack 側にも定義しておく。
*/
webpack: (config, { webpack }) => {
/**
* symbol-crypto-wasm-node を
* symbol-crypto-wasm-web に強制置換
*/
config.plugins.push(
new webpack.NormalModuleReplacementPlugin(
/symbol-crypto-wasm-node/,
"symbol-crypto-wasm-web/symbol_crypto_wasm.js",
),
);
/**
* WebAssembly を async import / top-level await で
* 正しく扱うための設定
*/
config.experiments = {
...config.experiments,
asyncWebAssembly: true,
topLevelAwait: true,
layers: true,
};
return config;
},
};
export default nextConfig;
※上記のように修正してもエラーが出る場合は一旦 npm run dev を止めて再実行してください。
本番ビルドでも確認
以下のコマンドで本番ビルド&起動したURLにアクセスしても動作することを確認します。
npm run build
npm run start
まとめ
- symbol-sdk v3 は Node 用 wasm 実装が優先される
- Turbopack では alias 設定が必須
- SSR / CSR 両方で動かす場合でも対応可能
