agentsclimarketplace

Vercel ssr 5xx cold render diagnosis

Skill bokuwalily/claude-code-skills/skills/vercel-ssr-5xx-cold-render-diagnosis

Vercel/Next.jsで「curlは200なのにGoogle Search Consoleのライブテスト/インデックス登録が5xxで弾かれる」時。aliasのキャッシュが症状を隠す問題、コールド初回レンダリングだけ落ちる断続5xx、jsdom依存ライブラリ(isomorphic-dompurify等)のERR_REQUIRE_ESMランタイムクラッシュの診断と修正。From its SKILL.md

Install
npx -y skills add bokuwalily/claude-code-skills --skill vercel-ssr-5xx-cold-render-diagnosis

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

4.8 KB, ~1.5k tokens by cl100k_base, as published. Nobody here has run it

Vercel SSR コールド5xx(curlは通るのにGSC/Googlebotが5xx)

Procedure

症状:GSCのURL検査ライブテストやサイトマップが「サーバーエラー(5xx)」「取得できませんでした」。 だが手元の curl https://<alias>.vercel.app/path は 200 を返す。

  1. aliasキャッシュに騙されるな。aliasのURLは前回ビルドのISR/SSGキャッシュ済みHTMLを配信するため200に見える。 実レンダリングを叩くには 生のデプロイURL を使う:

    npx vercel ls   # 生URL shukatsu-xxx-<hash>-<team>.vercel.app を取得
    # まだ一度もアクセスされてない動的パス(高ID等)をコールドで叩く
    GB="Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
    for id in 300 333 360 390; do
      curl -s -o /dev/null -w "%{http_code} /path/$id\n" -A "$GB" "https://<生URL>/path/$id"
    done
    

    コールド初回=500、リトライ=200 なら「オンデマンドISRレンダリングのクラッシュ」確定。

  2. 真因はランタイムログで取る(ブラウザのエラーHTMLでは分からない):

    ( timeout 25 npx vercel logs "https://<生URL>" & sleep 4; \
      curl -s -o /dev/null -A "$GB" "https://<生URL>/path/811"; wait ) \
      | grep -iE "error|exception|ERR_|require|esm|module"
    
  3. 典型的真因=jsdom依存ライブラリのESMクラッシュ

    Failed to load external module jsdom: ERR_REQUIRE_ESM:
    require() of ES Module .../@exodus/bytes/encoding-lite.js
    from html-encoding-sniffer ... not supported.
    

    犯人候補:isomorphic-dompurify(サーバーでjsdomをrequire)。SSRページのサニタイズ用途で混入しがち。

  4. 修正:jsdom依存を外し、純JS(htmlparser2ベース)の sanitize-html に置換する。 ⚠️ 正規表現でのHTMLサニタイズは回避可能でNG(自動セキュリティレビューでHIGH/MEDIUM XSSとして弾かれる)。 第一者コンテンツでも allowlist 方式の本物のサニタイザを使うこと。

    npm install sanitize-html @types/sanitize-html
    
    import sanitizeHtml from 'sanitize-html'
    const raw = marked.parse(content) as string
    return sanitizeHtml(raw, {
      allowedTags: ['h1','h2','h3','h4','h5','h6','p','a','ul','ol','li','blockquote',
        'strong','em','b','i','del','s','mark','sup','sub','code','pre','hr','br','span',
        'table','thead','tbody','tr','th','td','img'],
      allowedAttributes: { a:['href','name','target','rel','title'],
        img:['src','alt','title','width','height','loading'], h2:['id'], h3:['id'],
        th:['align'], td:['align'], span:['class'], code:['class'] },
      allowedSchemes: ['http','https','mailto'],
      allowedSchemesAppliedToAttributes: ['href','src'],
      transformTags: { a: sanitizeHtml.simpleTransform('a', { rel: 'noopener noreferrer' }, true) },
    })
    

    sanitize-html は jsdom を使わないので Vercel ランタイムで ERR_REQUIRE_ESM を起こさない(デプロイ後コールドで再検証必須)。 ユーザー入力(コメント等)は別問題。Reactの {value} は自動エスケープなので dangerouslySetInnerHTML を使ってなければ無対応でOK。

  5. デプロイ後、手順1のコールドテストで全200を確認してから完了宣言。

Pitfalls

  • aliasのcurlが200でも「直った」と判断しない。必ず生URL+未踏パスでコールド検証。
  • 同一構成の他プロジェクト(同アカウント・同SSR+同ライブラリ)も同じ病巣を持つ。横展開で確認。
  • 存在しないID/slugの404は正常(500と混同しない)。
  • marked.parse() は型が string | Promise<string>as string で受けてる既存コードに合わせる。
  • package.jsonからのdep削除はlockfile変動リスク。import除去だけでバンドルからは除外される(最小修正)。

Verification

GB="Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
for id in 300 333 360 390 410 440; do
  curl -s -o /dev/null -w "%{http_code} /articles/$id\n" -A "$GB" "https://<新生URL>/articles/$id"
done   # 全部200ならOK(存在しないIDの404は許容)

その後GSCで URL検査→インデックス登録をリクエスト が通ることを確認。

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Gives 0 of the 12 instructions most containers cloud skills give in ~1.5k tokens

Counted across 607 of the 657 authors here whose files we hold, read 2026-08-07

  • Run containers as a non-root userin 66 of 607, across 46 files
  • Use multi-stage buildsin 53 of 607, across 44 files
  • Use Promise.all for independent operationsin 47 of 607, across 13 files
  • Import directly instead of barrel filesin 46 of 607, across 12 files
  • Use ternary instead of AND for conditionalsin 45 of 607, across 12 files
  • Use Set or Map for O(1) lookupsin 42 of 607, across 10 files
  • Create a .dockerignore filein 41 of 607, across 31 files
  • Read individual rule files for detailsin 39 of 607, across 9 files
  • Copy dependency files before source codein 36 of 607, across 23 files
  • Authenticate server actions like API routesin 35 of 607, across 7 files
  • Use next/dynamic for heavy componentsin 34 of 607, across 9 files
  • Use React.cache for per-request deduplicationin 34 of 607, across 10 files

Said here and by no other author read

  • test raw deployment URLs not alias URLs
  • hit unvisited dynamic paths cold with Googlebot user agent
  • capture runtime logs while triggering cold render
  • replace jsdom dependencies with sanitize-html
  • use allowlist tags and attributes for sanitization
  • remove imports instead of deleting package dependencies

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 326,834. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.