2026年8月14日 金曜日
AI時短ラボ
活用· 約21

curlに-Lを付けずに308を叩くと15バイトの「Redirecting...」が返る──sitemapが0件・サイトが壊れたと誤診する罠

apex→wwwの308恒久リダイレクトを -L なしのcurlで叩くと、返るのは空ボディではなく Redirecting...(改行込み15バイト、Content-Type: text/plain)だった。そのため grep -c '<loc>' は0件、記事HTMLは15バイト、canonicalもJSON-LDも0件になり、sitemapが空・SSGが壊れたと3つまとめて誤診しうる。-L を付け直すと同じURLが200・125455バイト・<loc> 416件で、全部正常だった。2026-08-14にcurl 8.7.1 / macOS 15.1.1で再現した記録。

執筆・編集:
目次

結論から書く。apexドメイン(ai-jitan-hub.com)から www へ308でリダイレクトされるURLを -L なしの curl で叩くと、返ってくるのは空ボディではなく Redirecting... の15バイトだ。Content-Typetext/plain。だから grep -c '<loc>' は0を返し、HTMLを取れば「15バイト」になり、canonicalもJSON-LDも0件になる。sitemapが空なのでもSSGが壊れたのでもない。リダイレクトを追わない診断コマンドが、壊れていないものを壊れていると報告している。直し方は一つ、curl -sL にする。それだけで同じURLが200・125455バイト・<loc> 416件になった(2026-08-14、当サイト本番で確認)。

3行まとめ

  1. apex→wwwの308を -L なしで叩くと、ボディは空ではなく Redirecting...(改行込み15バイト、Content-Type: text/plain)が返る
  2. その15バイトを解析すると、<loc> 0件・HTML 15バイト・canonical 0件・JSON-LD 0件が同時に出て、「sitemapが空」「SSGが壊れた」「メタデータが飛んだ」と一式で誤診する
  3. curl -sL に直すと同じURLが200・125455バイト・<loc> 416件。壊れていたのはサイトではなく診断コマンドだった

症状

貼って検索されるのはこの文字列だと思うので、コマンドと出力をそのまま置く。

$ curl -s https://ai-jitan-hub.com/sitemap.xml
Redirecting...
$ curl -s -o /tmp/s.txt -w 'code=%{http_code} size=%{size_download}\n' https://ai-jitan-hub.com/sitemap.xml
code=308 size=15

$ xxd /tmp/s.txt
00000000: 5265 6469 7265 6374 696e 672e 2e2e 0a    Redirecting....

$ grep -c '<loc>' /tmp/s.txt
0

15バイトの内訳は Redirecting... の14文字と、末尾の改行(0a)1バイト。

レスポンスヘッダは次の通り(抜粋、2026-08-14取得)。

HTTP/2 308
content-type: text/plain
location: https://www.ai-jitan-hub.com/sitemap.xml
refresh: 0;url=https://www.ai-jitan-hub.com/sitemap.xml
server: Vercel

厄介なのは、症状がsitemapだけに出ないことだ。同じ308が全パスに掛かっているので、記事ページを叩いても15バイトが返る。2026-08-14に実際に出た値を並べる。

実行したこと -L なしで出た結果 その値だけを見て下しうる誤った診断 -L ありの実際
sitemap.xml を取得して grep -c '<loc>' code=308 / size=15 / 0件 sitemapが空。記事が1本も載っていない code=200 / size=125455 / 416件
記事ページのHTMLを取得 code=308 / size=15 HTMLが15バイト。SSGのビルドが壊れた code=200 / size=211626
同ページで grep -c 'rel="canonical"' 0件 canonicalが出力されていない 1件
同ページで grep -c 'application/ld+json' 0件 構造化データが全部飛んだ 2件
トップページを取得 code=308 / size=15 サイト全体が死んでいる (同じく308→www側で200)

4つの「壊れている証拠」が同時に出るので、かえって確信してしまう。原因は1つで、しかもサイト側ではない。

原因

噛み合っているのは3つ。

1. 308はボディを返してはいけない、とは書かれていない

MDNの308の説明はこうだ。

The HTTP 308 Permanent Redirect redirection response status code indicates that the requested resource has been permanently moved to the URL given by the Location header. A browser receiving this status will automatically request the resource at the URL in the Location header, redirecting the user to the new page.

(訳:HTTP 308 Permanent Redirect は、要求されたリソースが Location ヘッダで示されたURLへ恒久的に移動したことを示すリダイレクト応答ステータスコードである。このステータスを受け取ったブラウザは、Location ヘッダのURLのリソースを自動的に要求し、ユーザーを新しいページへリダイレクトする。)

主語が「A browser」であることに注意したい。自動的に追いかけるのはブラウザの振る舞いとして書かれているのであって、あらゆるHTTPクライアントがそうする、とは書かれていない。

同ページの応答例は Content-Length: 0(空ボディ)になっている。だが、これは例であって「ボディを空にせよ」という規定ではない。実際、当サイトのホストが返してきたのは15バイトのボディだった。「308なら空バイトのはず」という先入観が、15バイトを「壊れた応答」に見せる。

2. curlは -L を付けない限り追わない

curlのman pageの -L, --location はこう説明している。

(HTTP) If the server reports that the requested page has moved to a different location (indicated with a Location: header and a 3XX response code), this option makes curl redo the request to the new place.

(訳:(HTTP)要求されたページが別の場所へ移動したとサーバが報告した場合(Location: ヘッダと3XX応答コードで示される)、このオプションはcurlに新しい場所への再要求を行わせる。)

つまり -L は「リダイレクトを追う」オプションではなく、付けなければ追わないのが既定ということだ。ブラウザで開けば正常に見えるのに、curlのワンライナーだけが壊れて見える理由がここにある。ブラウザとcurlで既定の振る舞いが違う。

3. その308を出しているのは自分の設定ファイル

当サイトの next.config.tsredirects() に、apexへのアクセスをwwwへ寄せる規則が入っている(以下は該当箇所の抜粋。同ファイルには他の記事URL向けリダイレクトも並んでいる)。

async redirects() {
  return [
    {
      source: "/:path*",
      has: [{ type: "host", value: "ai-jitan-hub.com" }],
      destination: "https://www.ai-jitan-hub.com/:path*",
      permanent: true,
    },
  ];
}

permanent: true が308になる根拠は、手元にインストールされている next 16.2.7 の同梱ドキュメント(node_modules/next/dist/docs/01-app/03-api-reference/05-config/01-next-config-js/redirects.md)に明記されている。

permanent true or false - if true will use the 308 status code which instructs clients/search engines to cache the redirect forever, if false will use the 307 status code which is temporary and is not cached.

(訳:permanenttruefalsetrue なら308ステータスコードを使い、クライアントや検索エンジンにそのリダイレクトを永久にキャッシュするよう指示する。false なら307で、これは一時的でキャッシュされない。)

ホストで振り分ける has の書き方も同ドキュメントにある。

type: String - must be either header, cookie, host, or query.

(訳:type は文字列で、headercookiehostquery のいずれかでなければならない。)

さっきのヘッダに refresh: 0;url=... が混じっていたのも、同ドキュメントに書かれている。

To ensure IE11 compatibility, a Refresh header is automatically added for the 308 status code.

(訳:IE11との互換性を確保するため、308ステータスコードには Refresh ヘッダが自動的に付与される。)

なお、ボディの Redirecting... という文字列そのものは、この同梱ドキュメントにもMDNにも記載がないnode_modules/next/dist/Redirecting... でgrepしても、ヒットしたのはドキュメント内のJSXサンプル(return <p>Redirecting...</p>)だけで、実行コードには無かった。応答の server: Vercel から配信側が組み立てた文字列だと考えているが、これは確認できていない推測である(後述の但し書き参照)。

対処

① 追う:-L を付ける

$ curl -sL -o /tmp/s.txt -w 'code=%{http_code} size=%{size_download} url_effective=%{url_effective}\n' https://ai-jitan-hub.com/sitemap.xml
code=200 size=125455 url_effective=https://www.ai-jitan-hub.com/sitemap.xml

$ grep -c '<loc>' /tmp/s.txt
416

%{url_effective} を出力に足しておくと、「自分が今どのURLの中身を見ているのか」が毎回目に入る。man pageの定義はこうだ。

url_effective — The URL that was fetched last. This is most meaningful if you have told curl to follow location: headers.

(訳:url_effective — 最後に取得されたURL。curlに location: ヘッダを追うよう指示している場合に最も意味を持つ。)

② 追わずに気づく:%{redirect_url} を出力に足す

リダイレクトを追いたくない診断(ステータスコードだけ見たい場合など)でも、飛び先を表示すれば誤診は防げる。

$ curl -s -o /dev/null -w 'http_code=%{http_code} redirect_url=%{redirect_url}\n' https://ai-jitan-hub.com/sitemap.xml
http_code=308 redirect_url=https://www.ai-jitan-hub.com/sitemap.xml

redirect_url — When an HTTP request was made without --location to follow redirects (or when --max-redirs is met), this variable shows the actual URL a redirect would have gone to.

(訳:redirect_url — リダイレクトを追う --location なしでHTTP要求が行われた場合(または --max-redirs に達した場合)、この変数はリダイレクトが実際に向かうはずだったURLを示す。)

③ チェーンを目視する:curl -sIL

$ curl -sIL https://ai-jitan-hub.com/sitemap.xml | grep -iE '^HTTP|^location|^content-type'
HTTP/2 308
content-type: text/plain
location: https://www.ai-jitan-hub.com/sitemap.xml
HTTP/2 200
content-type: application/xml

content-typetext/plain から application/xml に変わるところまで見えると、1本目が中身ではないことが一目で分かる。

④ そもそも最終ホストを叩く

$ curl -s -o /dev/null -w 'http_code=%{http_code} size=%{size_download}\n' https://www.ai-jitan-hub.com/sitemap.xml
http_code=200 size=125455

運用として一番効いたのはこれだった。サイトを点検するスクリプトやワンライナーのベースURLを、リダイレクト元ではなく最終ホスト(当サイトなら https://www. 側)で持つ-L を付け忘れても壊れなくなる。

観測条件

  • 日付: 2026-08-14。応答ヘッダの dateFri, 14 Aug 2026 05:59:30 GMT
  • 環境: macOS 15.1.1(BuildVersion 24B91)//usr/bin/curl は curl 8.7.1(x86_64-apple-darwin24.0)、libcurl/8.7.1、nghttp2/1.62.0
  • 対象: 当サイトの本番(server: Vercel、Next.js 16.2.7、apex→wwwの308は next.config.tsredirects() 由来)
  • 再現: 本記事に載せたコマンドは、記事を書く時点で同日中に全て再実行し、同じ値が出ることを確認して数字を採った。<loc> 416件・sitemap 125455バイト・記事ページ211626バイトは、その再実行時点の実測値

ここで効く判断はひとつだ。ブラウザで開けば普通に表示されるのに、curlのワンライナーだけが0件を返す。その食い違いが出た時点で、まず疑うべきはサイトではなくコマンドの側になる。

正直な但し書き

  • ボディ文字列 Redirecting... の出所を公式に確認できていない。 next 16.2.7 の同梱ドキュメントにもMDNにも記載はなく、node_modules/next/dist/ のgrepでも実行コードには無かった。応答の server: Vercel から配信側の挙動だと推測しているが、推測であって確認された事実ではない
  • ローカルの next start で同じ15バイトが返るかは未検証。 本記事の観測はすべてVercel配信の本番ホストに対するもので、ローカルサーバでの再現は試していない
  • バイト数はサイトの中身が増減すれば変わる。 125455バイト・211626バイト・<loc> 416件を固定値として引用しないでほしい。再現性があるのは「-L なしで308を叩くと15バイトの Redirecting... が返り、grepが0件になる」という構造の方である
  • 308を返すサーバが全てこのボディを返すわけではない。 MDNの例は Content-Length: 0 の空ボディだ。空ボディを返す構成なら症状は「0バイト」になり、Redirecting... という文字列は出ない。その場合でも「grepが0件になる」誤診自体は同じように起きる
  • 多段リダイレクトは検証していない。 当サイトのリダイレクトはapex→wwwの1ホップだけで、--max-redirs に当たるケースや複数回転送される構成での挙動は確認していない
  • curlの引用は curl.se の最新man pageから取っており、手元の 8.7.1 の文面とは細部が違う。 -L の説明は最新版が「redo the request to the new place」、手元の curl 8.7.1 の man page は「redo the request on the new place」。redirect_url も最新版が「without --location」、8.7.1 は「without -L, --location」と表記が異なる。意味は変わらないが、引用は最新版の文面である
  • 診断時のコマンドログは保存していない。 本記事の数値は執筆時点で全て再実行して取り直したものであり、再現できるのは「-L なしで308を叩くと何が返るか」までである。当日どういう順番で何を勘違いしたかについては、記録として残しているものが無い
  • 本記事は当サイト1ドメイン・1環境での観測であり、他のホスティングや他のcurlバージョンで同じ文字列・同じバイト数になることを保証するものではない

出典

シェア: ポスト はてブ

出典・参照資料

AIニュースの解説を動画でも

YouTubeでは注目ニュースの背景を解説し、Xでは新着記事をお知らせしています。

コメント

まだコメントはありません。最初のコメントを書いてみませんか?

AIについて聞きたいことはありますか?

質問箱で無料で受け付けています。回答は公開され、他の方の参考にもなります。

質問箱を見る →

新しい記事をメールで受け取る

AIの新しい発表を、出典付きで整理して届けます。

関連記事

AI時短ラボ
活用08.14読了26

出典URLの死活チェックで404だけ見てはいけない──1,517件を全数検査したら、HEADに404を返す公式ドキュメントと403の一次資料が出てきた

出典 ─ RFC 9110: HTTP Semanti
AI時短ラボ
活用08.14読了36

Vercelの自動生成ドメインがGSCの参照元に117本入っていた──*.vercel.appと「Duplicate, Google chose different canonical than user」を全351記事で実測

出典 ─ Accessing Deployments
Vercelデプロイのエラーを減らす個人開発チェックリスト──ローカルビルドから本番200確認までの記事画像
活用07.16読了7

Vercelデプロイのエラーを減らす個人開発チェックリスト──ローカルビルドから本番200確認まで

出典 ─ Vercel Docs: Troublesh
「AIっぽい見た目」を卒業する──デザイントークンをAIに渡す方法の記事画像
活用06.01読了11

「AIっぽい見た目」を卒業する──デザイントークンをAIに渡す方法

出典 ─ Design Tokens Communit
Claude Codeがエラーで動かない時の切り分け手順──400・401・403は「認証の優先順位」を疑うの記事画像
活用08.14読了22

Claude Codeがエラーで動かない時の切り分け手順──400・401・403は「認証の優先順位」を疑う

出典 ─ Claude Code公式ドキュメント
AI時短ラボ
活用08.14読了23

ffmpegのエンコード指定を出力ファイル名より後ろに置くと効かない──96000Hzのまま連結して尺が43秒に化けた話

出典 ─ ffmpeg Documentation -
AI時短ラボ
活用08.14読了31

URL Inspection APIのレスポンスを機械判定すると壊れる3点──coverageStateは表示文字列、ROBOTS_TXT_STATE_UNSPECIFIEDはrobots.txtの問題ではない

出典 ─ Method: index.inspect
AI時短ラボ
活用08.14読了35

www化の308を入れてもSearch Consoleの旧プロパティは集計を続ける──URLプレフィックス型で5週間データを取り逃した記録

出典 ─ Add a website or platf