T-CREATOR

<div />

WebAssembly が読み込めない時の典型原因:MIME 設定・CORS・パス解決

WebAssembly が読み込めない時の典型原因:MIME 設定・CORS・パス解決

WebAssembly(Wasm)をブラウザで動かそうとしたとき、突然のエラーで読み込みが失敗した経験はありませんか?開発環境では問題なく動いていたのに、本番環境や CDN 経由でアクセスすると動かなくなる…そんなトラブルに遭遇した方も多いのではないでしょうか。

この記事では、WebAssembly が読み込めない時に頻発する 3 つの典型的な原因—MIME 設定の不備CORS ポリシー違反パス解決の失敗—に焦点を当て、それぞれの具体的なエラーコードから解決方法まで、実践的に解説します。デバッグに苦労している方、これから本番環境へデプロイする方にとって、必ず役立つ内容です。

背景

WebAssembly の基本的な読み込みフロー

WebAssembly モジュールをブラウザで実行するには、通常以下のような手順を踏みます。

javascript// 基本的なWebAssembly読み込みパターン
fetch('module.wasm')
  .then((response) => response.arrayBuffer())
  .then((bytes) => WebAssembly.instantiate(bytes))
  .then((result) => {
    // WebAssemblyモジュールを使用
    const exports = result.instance.exports;
  });

このシンプルな流れの中で、実は複数のチェックポイントが存在します。ブラウザは.wasmファイルを取得する際、HTTP ヘッダーを確認し、セキュリティポリシーを検証し、ファイルパスを解決します。

ブラウザとサーバーの役割分担

WebAssembly の読み込みは、クライアント(ブラウザ)とサーバーの連携によって成立しますが、それぞれが異なる責任を持っています。

mermaidflowchart TD
  browser["ブラウザ"] -->|リクエスト| server["Webサーバー"]
  server -->|レスポンス<br/>Content-Type確認| browser
  browser -->|CORS検証| security["セキュリティポリシー"]
  security -->|検証OK| parse["Wasmパース"]
  security -->|検証NG| error1["CORSエラー"]
  parse -->|成功| execute["実行"]
  parse -->|失敗| error2["MIME/パースエラー"]

このフローの各段階で問題が発生すると、WebAssembly は読み込めません。特に本番環境では、開発時には気づかなかった設定不備が顕在化しやすいのです。

開発環境と本番環境の違い

開発時に使うwebpack-dev-serverViteなどのローカルサーバーは、WebAssembly に適した設定が自動で行われることが多いです。しかし、Nginx、Apache、CDN などの本番環境では、明示的な設定が必要になります。

課題

典型的なエラーパターンとその影響

WebAssembly の読み込み失敗は、開発者を悩ませる代表的なトラブルです。以下のようなエラーメッセージに直面したことがある方も多いでしょう。

textUncaught (in promise) TypeError: Failed to fetch
textUncaught (in promise) TypeError: Incorrect response MIME type. Expected 'application/wasm'.
textCross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource

これらのエラーは一見似ていますが、原因はそれぞれ異なります。適切に対処するには、各エラーの背景を理解することが不可欠です。

問題の分類と診断フロー

WebAssembly 読み込みエラーは、大きく分けて以下の 3 つに分類できます。

#エラー分類主な症状発生タイミング
1MIME 設定エラー「Incorrect response MIME type」fetch後のパース時
2CORS エラー「Cross-Origin Request Blocked」リクエスト送信時
3パス解決エラー「Failed to fetch」「404 Not Found」リクエスト前/送信時

診断する際は、ブラウザの開発者ツール(DevTools)の Network タブと Console タブを同時に確認するのが効果的です。

mermaidflowchart LR
  start["エラー発生"] --> check1{"ネットワークタブで<br/>ステータスコード確認"}
  check1 -->|404| path_issue["パス解決エラー"]
  check1 -->|200| check2{"Consoleで<br/>エラー内容確認"}
  check2 -->|MIME type| mime_issue["MIME設定エラー"]
  check2 -->|CORS| cors_issue["CORSエラー"]
  check2 -->|その他| other["その他の問題"]

このフローに従って診断すれば、問題を素早く特定できます。

解決策

問題 1:MIME 設定エラーの解決

エラーコードと発生条件

エラーコード: TypeError

エラーメッセージ:

textUncaught (in promise) TypeError: Incorrect response MIME type. Expected 'application/wasm'.

発生条件:

  • Web サーバーが Wasm ファイルをapplication​/​wasm以外の Content-Type で返している
  • サーバー設定で.wasm拡張子に対する MIME type が未定義
  • デフォルトでapplication​/​octet-streamtext​/​plainとして配信されている

解決方法

ステップ 1:現在の MIME type を確認する

まず、DevTools の Network タブで実際に返されている Content-Type を確認しましょう。

javascript// ブラウザコンソールでContent-Typeを確認
fetch('module.wasm').then((response) => {
  console.log(
    'Content-Type:',
    response.headers.get('Content-Type')
  );
  return response;
});

正しく設定されていれば、application​/​wasmが表示されます。

ステップ 2:Nginx での設定

Nginx を使用している場合、mime.typesファイルまたは設定ファイルに以下を追加します。

nginx# /etc/nginx/mime.types または nginx.conf に追加

types {
    application/wasm wasm;
}

または、nginx.conf内で直接指定することも可能です。

nginx# nginx.confのhttpブロック内

http {
    include mime.types;

    # wasmファイル用の追加設定
    types {
        application/wasm wasm;
    }
}

設定後は必ず Nginx を再起動します。

bashsudo nginx -t  # 設定ファイルの構文チェック
sudo systemctl restart nginx
ステップ 3:Apache での設定

Apache の場合は、.htaccessファイルまたはhttpd.confに追加します。

apache# .htaccessファイルに追加

<IfModule mod_mime.c>
    AddType application/wasm .wasm
</IfModule>

または、Apache の設定ファイルに直接記述する方法もあります。

apache# httpd.confまたはapache2.conf

<IfModule mime_module>
    AddType application/wasm .wasm
</IfModule>

設定後、Apache を再起動します。

bashsudo apachectl configtest  # 設定ファイルチェック
sudo systemctl restart apache2
ステップ 4:Node.js(Express)での設定

開発環境で Node.js の Express を使用している場合は、ミドルウェアで対応できます。

javascriptconst express = require('express');
const app = express();

// MIMEタイプを設定するミドルウェア
app.use((req, res, next) => {
  if (req.url.endsWith('.wasm')) {
    res.type('application/wasm');
  }
  next();
});

より明示的に設定する場合は、express.staticと組み合わせます。

javascriptconst path = require('path');

// 静的ファイル配信の設定
app.use(
  express.static('public', {
    setHeaders: (res, filePath) => {
      if (filePath.endsWith('.wasm')) {
        res.set('Content-Type', 'application/wasm');
      }
    },
  })
);
ステップ 5:CDN での設定

Cloudflare や AWS CloudFront などの CDN を使用している場合、CDN 側でも Content-Type の設定が必要です。

Cloudflare の場合:

  • 通常は自動的に正しい MIME type を返しますが、Custom Rules で明示的に設定することも可能

AWS CloudFront の場合:

  • S3 バケットにアップロードする際、オブジェクトメタデータで Content-Type を指定
bash# AWS CLIでS3にアップロード時にContent-Typeを指定
aws s3 cp module.wasm s3://your-bucket/module.wasm \
  --content-type application/wasm

問題 2:CORS エラーの解決

エラーコードと発生条件

エラーコード: CORS policy error

エラーメッセージ:

textAccess to fetch at 'https://example.com/module.wasm' from origin 'https://your-site.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

または

textCross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://example.com/module.wasm

発生条件:

  • WebAssembly ファイルを異なるドメインから読み込もうとしている
  • CDN から配信している場合
  • サブドメインが異なる場合(www.example.comapi.example.comなど)
  • サーバーが CORS ヘッダーを返していない

解決方法

ステップ 1:CORS エラーの確認

DevTools の Console タブでエラーメッセージを確認し、Network タブで該当リクエストのレスポンスヘッダーを調べます。

javascript// ブラウザコンソールでCORSヘッダーを確認
fetch('https://cdn.example.com/module.wasm')
  .then((response) => {
    console.log(
      'CORS Header:',
      response.headers.get('Access-Control-Allow-Origin')
    );
  })
  .catch((error) => console.error('CORS Error:', error));
ステップ 2:Nginx での cors 設定

Nginx では、add_headerディレクティブを使用して CORS ヘッダーを追加します。

nginx# nginx.confのserverブロック内

location ~* \.wasm$ {
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods "GET, OPTIONS";
    add_header Access-Control-Allow-Headers "Content-Type";
}

特定のオリジンのみ許可する場合は、ワイルドカード*を具体的なドメインに置き換えます。

nginxlocation ~* \.wasm$ {
    add_header Access-Control-Allow-Origin "https://your-site.com";
    add_header Access-Control-Allow-Credentials "true";
}

プリフライトリクエスト(OPTIONS)にも対応する完全な設定は以下の通りです。

nginxlocation ~* \.wasm$ {
    if ($request_method = 'OPTIONS') {
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods "GET, OPTIONS";
        add_header Access-Control-Allow-Headers "Content-Type";
        add_header Access-Control-Max-Age 3600;
        add_header Content-Length 0;
        add_header Content-Type text/plain;
        return 204;
    }

    add_header Access-Control-Allow-Origin *;
}
ステップ 3:Apache での cors 設定

Apache では、mod_headersモジュールを使用します。

apache# .htaccessファイル

<IfModule mod_headers.c>
    <FilesMatch "\.wasm$">
        Header set Access-Control-Allow-Origin "*"
        Header set Access-Control-Allow-Methods "GET, OPTIONS"
    </FilesMatch>
</IfModule>

特定オリジンのみ許可する場合は、以下のように記述します。

apache<FilesMatch "\.wasm$">
    SetEnvIf Origin "^https://your-site\.com$" ORIGIN_ALLOWED=$0
    Header set Access-Control-Allow-Origin "%{ORIGIN_ALLOWED}e" env=ORIGIN_ALLOWED
</FilesMatch>
ステップ 4:Node.js(Express)での cors 設定

Express で CORS を有効にするには、corsミドルウェアを使用するのが簡単です。

javascriptconst express = require('express');
const cors = require('cors');
const app = express();

// すべてのオリジンを許可
app.use(cors());

Wasm ファイルのみに限定する場合は、カスタムミドルウェアで対応します。

javascriptapp.use((req, res, next) => {
  if (req.url.endsWith('.wasm')) {
    res.header('Access-Control-Allow-Origin', '*');
    res.header(
      'Access-Control-Allow-Methods',
      'GET, OPTIONS'
    );
  }
  next();
});

特定のオリジンのみ許可する詳細な設定も可能です。

javascriptconst corsOptions = {
  origin: 'https://your-site.com',
  optionsSuccessStatus: 200,
};

app.use(
  '/wasm',
  cors(corsOptions),
  express.static('wasm-files')
);
ステップ 5:CDN での cors 設定

AWS CloudFrontでは、Cache Behavior の設定で CORS ヘッダーを転送する必要があります。

  1. CloudFront ディストリビューションの設定を開く
  2. Behavior 設定で Cache Policy/Origin Request Policy を設定
  3. Originヘッダーをホワイトリストに追加
  4. S3 オリジンで CORS ルールを設定

Cloudflareでは、Transform Rules を使用してヘッダーを追加できます。

javascript// Cloudflare Workers でのCORS設定例
addEventListener('fetch', (event) => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  const response = await fetch(request);
  const newResponse = new Response(response.body, response);

  if (request.url.endsWith('.wasm')) {
    newResponse.headers.set(
      'Access-Control-Allow-Origin',
      '*'
    );
  }

  return newResponse;
}

問題 3:パス解決エラーの解決

エラーコードと発生条件

エラーコード: 404 Not FoundTypeError

エラーメッセージ:

textGET https://example.com/module.wasm 404 (Not Found)
textUncaught (in promise) TypeError: Failed to fetch

発生条件:

  • 相対パスが正しく解決されていない
  • ビルド時のパブリックパス設定が不適切
  • Webpack やバンドラーの出力設定ミス
  • SPA のルーティングと衝突している

解決方法

ステップ 1:パスの確認とデバッグ

まず、実際にブラウザがどの URL にアクセスしようとしているか確認します。

javascript// デバッグ用:実際のパスを出力
const wasmPath = './module.wasm';
console.log(
  'Attempting to load:',
  new URL(wasmPath, window.location.href).href
);

fetch(wasmPath)
  .then((response) => console.log('Success:', response.url))
  .catch((error) => console.error('Failed:', error));

DevTools の Network タブでも、失敗したリクエストの URL を確認できます。

ステップ 2:相対パスと絶対パスの使い分け

相対パスには、カレントディレクトリ基準(.​/​)とドキュメントルート基準(​/​)があります。

javascript// カレントディレクトリ基準(現在のHTMLファイルからの相対)
fetch('./wasm/module.wasm');

// ドキュメントルート基準(サイトのルートからの絶対パス)
fetch('/assets/wasm/module.wasm');

// 完全なURL(CDNなど)
fetch('https://cdn.example.com/module.wasm');

SPA の場合、ルーティングの影響でカレントディレクトリが変わることがあるため、ドキュメントルート基準が安全です。

ステップ 3:Webpack での設定

Webpack を使用している場合、publicPathの設定が重要です。

javascript// webpack.config.js

module.exports = {
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].bundle.js',
    publicPath: '/', // ルートからの絶対パス
  },
};

Wasm ファイルを特定のディレクトリにコピーする設定も追加します。

javascriptconst CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = {
  plugins: [
    new CopyWebpackPlugin({
      patterns: [
        {
          from: 'src/wasm/module.wasm',
          to: 'wasm/module.wasm',
        },
      ],
    }),
  ],
};

Webpack 5 では、Wasm の取り扱いにexperiments設定を使います。

javascriptmodule.exports = {
  experiments: {
    asyncWebAssembly: true,
    syncWebAssembly: true,
  },
};
ステップ 4:動的なパス解決

環境変数やビルド設定を使って、動的にパスを解決する方法もあります。

javascript// 環境に応じたベースURLの設定
const getWasmUrl = (filename) => {
  const baseUrl =
    process.env.NODE_ENV === 'production'
      ? 'https://cdn.example.com/wasm'
      : '/wasm';
  return `${baseUrl}/${filename}`;
};

// 使用例
fetch(getWasmUrl('module.wasm')).then((response) =>
  response.arrayBuffer()
);

Next.js のような現代的なフレームワークでは、publicディレクトリを活用します。

javascript// Next.jsでの例
// public/wasm/module.wasm として配置

const wasmUrl = '/wasm/module.wasm';
fetch(wasmUrl).then((response) => response.arrayBuffer());
ステップ 5:サーバー側のルーティング設定

SPA を使用している場合、サーバー側で Wasm ファイルへのルーティングを適切に設定する必要があります。

Nginx での設定:

nginx# SPAのルーティング設定とWasmファイルの配信を両立
location / {
    try_files $uri $uri/ /index.html;
}

location /wasm {
    alias /var/www/html/wasm;
    types {
        application/wasm wasm;
    }
}

Express(Node.js)での設定:

javascriptconst express = require('express');
const path = require('path');
const app = express();

// 静的ファイル(Wasmを含む)の配信
app.use(
  '/wasm',
  express.static(path.join(__dirname, 'public/wasm'))
);

// SPAのフォールバック(すべてのルートをindex.htmlに)
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'public/index.html'));
});

ここで注意すべきは、Wasm ファイルの配信設定を、SPA のフォールバック設定よりも先に記述することです。

具体例

ケーススタディ 1:開発環境では動くが本番で失敗する

状況

ローカルの開発サーバー(webpack-dev-server)では正常に動作する WebAssembly アプリケーションが、Nginx で動かす本番環境では以下のエラーが発生しました。

textUncaught (in promise) TypeError: Incorrect response MIME type. Expected 'application/wasm'.

診断手順

DevTools の Network タブで確認すると、Content-Type がapplication​/​octet-streamになっていました。

javascript// ブラウザコンソールでの確認
fetch('https://example.com/app.wasm').then((r) =>
  console.log(r.headers.get('Content-Type'))
);
// 出力: "application/octet-stream"  ← 間違い

解決の流れ

以下の図は、問題解決のフローを示しています。

mermaidflowchart TD
  problem["本番環境でWasmが<br/>読み込めない"] --> check["NetworkタブでContent-Type確認"]
  check --> found["application/octet-stream<br/>を検出"]
  found --> solution1["Nginxのmime.types<br/>を編集"]
  solution1 --> add["application/wasm wasm<br/>を追加"]
  add --> restart["Nginxサービス再起動"]
  restart --> verify["動作確認"]
  verify --> success["読み込み成功"]

実際の対処:

nginx# /etc/nginx/mime.types に追加
types {
    # 既存のMIME type設定
    text/html html htm shtml;
    text/css css;
    # ... 他の設定 ...

    # WebAssembly用に追加
    application/wasm wasm;
}

サービスを再起動して確認します。

bashsudo nginx -t
sudo systemctl restart nginx

再度ブラウザで確認すると、正しくapplication​/​wasmが返されるようになりました。

ケーススタディ 2:CDN から読み込む際の CORS エラー

状況

パフォーマンス向上のため、WebAssembly ファイルを CDN(CloudFront)に配置したところ、以下のエラーが発生しました。

textAccess to fetch at 'https://d1234567890.cloudfront.net/module.wasm' from origin 'https://myapp.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

診断手順

DevTools で確認すると、レスポンスヘッダーにAccess-Control-Allow-Originが含まれていませんでした。

javascript// ヘッダーの確認
fetch(
  'https://d1234567890.cloudfront.net/module.wasm'
).then((r) => {
  console.log(
    'CORS Header:',
    r.headers.get('Access-Control-Allow-Origin')
  );
  // 出力: null  ← CORSヘッダーがない
});

解決の流れ

CloudFront と S3 の両方で設定が必要です。

ステップ 1: S3 バケットの CORS 設定

json[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET", "HEAD"],
    "AllowedOrigins": ["*"],
    "ExposeHeaders": [],
    "MaxAgeSeconds": 3600
  }
]

S3 コンソールのバケット設定で、「アクセス許可」→「CORS の設定」から上記 JSON を設定します。

ステップ 2: CloudFront の Cache Policy 設定

  1. CloudFront ディストリビューションを開く
  2. Behavior タブで該当の Behavior を編集
  3. Cache Policy で「CachingOptimized」を選択するか、カスタムポリシーを作成
  4. Origin Request Policy で「CORS-S3Origin」を選択

ステップ 3: オブジェクトのメタデータ設定

新規アップロード時に Content-Type も同時に設定します。

bashaws s3 cp module.wasm s3://my-bucket/wasm/module.wasm \
  --content-type application/wasm \
  --metadata-directive REPLACE \
  --acl public-read

CloudFront のキャッシュをクリアして再確認します。

bashaws cloudfront create-invalidation \
  --distribution-id E1234567890ABC \
  --paths "/wasm/*"

この対処により、CORS ヘッダーが正しく返されるようになりました。

ケーススタディ 3:SPA でのパス解決の問題

状況

React SPA アプリケーションで、ルーティングを使用している環境で WebAssembly が読み込めませんでした。

textGET https://myapp.com/dashboard/module.wasm 404 (Not Found)

トップページ(https:​/​​/​myapp.com​/​)では動作するのに、サブページ(https:​/​​/​myapp.com​/​dashboard​/​)では失敗します。

診断手順

問題は相対パスの解決にありました。コードでは以下のように記述していました。

javascript// 問題のあるコード
fetch('./module.wasm'); // カレントディレクトリ基準

トップページではhttps:​/​​/​myapp.com​/​module.wasmとして解決されますが、​/​dashboard​/​ではhttps:​/​​/​myapp.com​/​dashboard​/​module.wasmになってしまいます。

解決の流れ

mermaidflowchart TD
  top["トップページ<br/>myapp.com/"] --> relative1["./module.wasm"]
  relative1 --> resolve1["myapp.com/module.wasm<br/>✓ 成功"]

  sub["サブページ<br/>myapp.com/dashboard/"] --> relative2["./module.wasm"]
  relative2 --> resolve2["myapp.com/dashboard/module.wasm<br/>✗ 404エラー"]

  sub2["サブページ(修正後)<br/>myapp.com/dashboard/"] --> absolute["/module.wasm"]
  absolute --> resolve3["myapp.com/module.wasm<br/>✓ 成功"]

解決策 1: 絶対パスを使用

javascript// ドキュメントルート基準に変更
fetch('/module.wasm'); // 常にルートから解決

解決策 2: 環境変数でベース URL を管理

javascript// .env ファイル
REACT_APP_WASM_BASE_URL=/wasm

// アプリケーションコード
const wasmBaseUrl = process.env.REACT_APP_WASM_BASE_URL || '/wasm';
const wasmPath = `${wasmBaseUrl}/module.wasm`;

fetch(wasmPath)
  .then(response => response.arrayBuffer());

解決策 3: Webpack の public path 設定

javascript// webpack.config.js
module.exports = {
  output: {
    publicPath: '/',
  },
  plugins: [
    new CopyWebpackPlugin({
      patterns: [{ from: 'public/wasm', to: 'wasm' }],
    }),
  ],
};

React Router を使っている場合、basenameプロパティとも整合性を取る必要があります。

javascript// App.js
import { BrowserRouter } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter basename='/'>
      {/* ルート定義 */}
    </BrowserRouter>
  );
}

これらの対処により、どのルートからでも正しく WebAssembly ファイルを読み込めるようになりました。

デバッグチェックリスト

トラブルシューティングの際に確認すべき項目を表にまとめます。

#チェック項目確認方法期待値
1HTTP ステータスコードDevTools > Network200 OK
2Content-TypeResponse Headersapplication/wasm
3Access-Control-Allow-OriginResponse Headers* または自ドメイン
4リクエスト URLNetwork > Name 列正しいパス
5ファイルサイズNetwork > Size 列0 でない値
6キャッシュステータスResponse Headers必要に応じて確認

これらを順番にチェックすることで、問題を効率的に特定できます。

まとめ

WebAssembly が読み込めない問題は、主にMIME 設定CORSパス解決の 3 つに集約されます。それぞれの問題には明確なエラーメッセージと解決方法があり、適切な診断フローに従えば確実に解決できます。

重要なポイント:

  1. MIME 設定: Web サーバーで.wasmファイルに対してapplication​/​wasmを明示的に設定する
  2. CORS: クロスオリジンでの読み込みにはAccess-Control-Allow-Originヘッダーが必須
  3. パス解決: SPA では絶対パスを使用し、ビルドツールの設定も確認する

開発環境と本番環境の違いを意識し、デプロイ前に必ず本番相当の環境でテストすることが、トラブルを未然に防ぐ最善の方法です。DevTools の Network タブと Console タブを活用すれば、ほとんどの問題は数分で診断できるでしょう。

この記事で紹介した診断フローと解決策を参考に、皆さんの WebAssembly アプリケーションが快適に動作することを願っています。

関連リンク

;