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
npx -y skills add bokuwalily/claude-code-skills --skill vercel-ssr-5xx-cold-render-diagnosisAssembled 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 を返す。
-
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レンダリングのクラッシュ」確定。
-
真因はランタイムログで取る(ブラウザのエラー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" -
典型的真因=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ページのサニタイズ用途で混入しがち。 -
修正:jsdom依存を外し、純JS(htmlparser2ベース)の
sanitize-htmlに置換する。 ⚠️ 正規表現でのHTMLサニタイズは回避可能でNG(自動セキュリティレビューでHIGH/MEDIUM XSSとして弾かれる)。 第一者コンテンツでも allowlist 方式の本物のサニタイザを使うこと。npm install sanitize-html @types/sanitize-htmlimport 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。 -
デプロイ後、手順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.