kt-tech.blog

画像

【トラブルシューティング】Cloudflare Pages → Workers 移行で遭遇したEdge Runtime問題集

Share
📌
概要: Cloudflare PagesからWorkersに移行する際に遭遇したEdge Runtime関連の問題と解決策をまとめました。cryptoモジュール問題、ライブラリ互換性、CPU制限、503エラーなど、実際に踏んだ地雷とその回避方法を記録しています。

[toc]

はじめに

  • microCMSからNotion APIへの移行に伴い、デプロイ基盤もCloudflare PagesからWorkersに刷新した

  • @cloudflare/next-on-pagesのEdge Runtime制約では Notion APIクライアント等のNode.js依存パッケージが動かない

  • 最終的にOpenNext(@opennextjs/cloudflare)で解決したが、移行過程で多くの問題に遭遇した

  • この記事では、各問題の原因と解決策を時系列で記録する

1. crypto モジュール問題

症状

Error: Module not found: Can't resolve 'crypto'

@notionhq/client(Notion公式SDK)がNode.jsのcryptoモジュールに依存しており、Edge Runtimeでビルドエラーが発生。

原因

Cloudflare Pages(Edge Runtime)ではNode.jsのビルトインモジュール(crypto, fs, path等)が使用できない。@notionhq/clientは内部でリクエスト署名にcryptoを使用している。

解決策: Notion REST APIを直接fetchで呼び出し

SDKを使わず、Notion REST APIを直接fetchで呼び出す方式に変更した。

// libs/notion.ts — SDK不使用版
const NOTION_API_BASE = 'https://api.notion.com/v1';
const headers = {
  'Authorization': `Bearer ${process.env.NOTION_TOKEN}`,
  'Notion-Version': '2022-06-28',
  'Content-Type': 'application/json',
};

export async function queryDatabase(databaseId: string, filter?: any) {
  const response = await fetch(`${NOTION_API_BASE}/databases/${databaseId}/query`, {
    method: 'POST',
    headers,
    body: JSON.stringify({ filter }),
  });
  return response.json();
}
📌
: 最終的にOpenNextに移行したため、SDKを使う方式に戻した。この方法はEdge Runtimeで動かす必要がある場合の回避策。

2. zenn-markdown-html の crypto依存

症状

Module not found: Can't resolve 'crypto'
  at node_modules/zenn-markdown-html/...

ブログのMarkdownレンダリングに使用していたzenn-markdown-htmlがNode.jsのcryptoに依存していた。

原因

zenn-markdown-htmlは内部でMarkdownのハッシュ生成やKaTeX処理にcryptoモジュールを使用している。

解決策: markdown-itに置換

zenn-markdown-htmlmarkdown-itに置き換えた。markdown-itはブラウザ互換で、Edge Runtimeでも動作する。

// Before
import markdownToHtml from 'zenn-markdown-html';
const html = markdownToHtml(body);

// After
import MarkdownIt from 'markdown-it';
const md = new MarkdownIt({
  html: true,
  highlight: (str, lang) => {
    // highlight.jsでシンタックスハイライト
  },
});
const html = md.render(body);

3. cheerio の Node.js依存

症状

Module not found: Can't resolve 'stream'
  at node_modules/cheerio/...

目次生成やリンクカード作成に使用していたcheerioがNode.jsのstreamモジュールに依存していた。

原因

cheerioはhtmlparser2をベースにしており、Node.jsのstreamモジュールに依存。Edge Runtimeでは利用不可。

解決策: 正規表現ベースのHTML操作に書き換え

// Before: cheerioでの目次生成
import cheerio from 'cheerio';
const $ = cheerio.load(html);
const headings = $('h2, h3').map((_, el) => ({
  id: $(el).attr('id'),
  text: $(el).text(),
  level: parseInt(el.tagName[1]),
})).get();

// After: 正規表現での目次生成
function extractHeadings(html: string) {
  const regex = /<h([23])[^>]*id="([^"]*)">([^<]*)<\/h[23]>/g;
  const headings = [];
  let match;
  while ((match = regex.exec(html)) !== null) {
    headings.push({
      level: parseInt(match[1]),
      id: match[2],
      text: match[3],
    });
  }
  return headings;
}

4. highlight.js は実はEdge互換

当初の認識(間違い)

highlight.jsもEdge Runtimeで動かないと思い込んでいた。

実際の原因

highlight.js自体はEdge Runtime互換。問題だったのはcheerio経由で使っていたこと。cheerioがNode.js依存だったため、巻き添えでエラーになっていた。

解決策: markdown-itのhighlightオプションで直接使用

import hljs from 'highlight.js';
import MarkdownIt from 'markdown-it';

const md = new MarkdownIt({
  html: true,
  highlight: (str: string, lang: string) => {
    if (lang && hljs.getLanguage(lang)) {
      try {
        return `<pre class="hljs"><code>${hljs.highlight(str, { language: lang }).value}</code></pre>`;
      } catch (__) {}
    }
    return `<pre class="hljs"><code>${md.utils.escapeHtml(str)}</code></pre>`;
  },
});

cheerioを排除したことで、highlight.jsは問題なく動作するようになった。

5. Workers CPU制限 Error 1102

症状

Error 1102: Worker exceeded CPU time limit

デプロイ後、すべてのページで1102エラーが返される。

原因

Cloudflare Workers無料プランのCPU時間制限は10ms。Next.jsのSSRレンダリングは通常50ms〜200msのCPU時間を必要とするため、無料プランでは実行できない。

解決策: $5/月の有料プランにアップグレード

# Cloudflareダッシュボード → Workers & Pages → Plans
# Workers Paid ($5/month) にアップグレード

有料プランではCPU時間が30秒まで拡張され、Next.jsのSSRが問題なく動作する。

6. 503エラー大量発生

症状

有料プランにアップグレード後も、全ページで503エラーが返される。Workerログには以下のエラー:

Error: Environment variable NOTION_TOKEN is not set

原因

Worker Secretsが未設定だった。GitHub Actionsのsecretsはビルド時のみ有効で、ランタイムのWorkerには渡されない。Workerが実行時に参照する環境変数はWorker Secretsとして別途設定する必要がある。

解決策: wrangler secret putで環境変数をWorkerに直接設定

# Worker Secretsに必要な環境変数を設定
echo "ntn_xxxxxxxxxxxxx" | npx wrangler secret put NOTION_TOKEN
echo "2eca0ffb-xxxx-xxxx-xxxx-xxxxxxxxxxxx" | npx wrangler secret put NOTION_DATABASE_ID
echo "G-XXXXXXXXXX" | npx wrangler secret put NEXT_PUBLIC_GA_ID
echo "https://kt-tech.blog" | npx wrangler secret put SITE_URL

wrangler.toml vs Worker Secrets

設定方法 用途 セキュリティ
wrangler.toml [vars] 公開して良い設定値 Gitにコミットされる
wrangler secret put APIキー等のシークレット 暗号化して保存
GitHub Actions secrets ビルド時の環境変数 ビルド時のみ有効

Tips

📌
Tip 1: OpenNext(@opennextjs/cloudflare)を使えばNode.js互換モードで動くため、Edge Runtime制約から解放される。最初からOpenNextを選択していれば問題1〜4は発生しなかった
📌
Tip 2: Edge Runtime互換かどうかを事前に確認するには、npx edge-runtime --eval "require('package-name')"でテストできる
📌
Tip 3: Workers CPU制限のエラーは、ローカルのwrangler devでは再現しない。必ず本番環境でテストすること
📌
Tip 4: 503エラーが出たら、まずwrangler tailでリアルタイムログを確認。環境変数の未設定が原因であることが多い

まとめ

  • Cloudflare PagesのEdge Runtime制約はNode.js依存パッケージを多用するプロジェクトには厳しい

  • crypto依存は@notionhq/clientだけでなく、zenn-markdown-html等の間接依存にも潜んでいる

  • cheerioの代替は正規表現zenn-markdown-htmlの代替はmarkdown-itで対応可能

  • highlight.jsはEdge互換。cheerioとの組み合わせが問題だっただけ

  • Workers無料プランのCPU 10ms制限はNext.jsには不足。$5/月の有料プランが必須

  • Worker Secretsの設定忘れは503エラーの最も多い原因。ビルド時の環境変数とランタイムの環境変数は別物