結論
- Next.js(App Router)は、GETを定義するだけでHEADメソッドを自動実装します
- ただし公式ドキュメントにはこの挙動について記載がありませんでした(OPTIONSの自動実装のみ明記)
- ソースコード確認と実機検証(Next.js v16.2.6)で動作を確認しました
きっかけ
ネットワークはなぜつながるのか 第2版 を読んでいてHTTPメソッドの整理をしていたとき、Next.jsのRoute HandlerでHEADを定義しなかった場合にどうなるのかが気になりました。
Claude Desktopに聞いたところ「HEADは自動実装されない」と回答されました。公式ドキュメントにも記載がなかったため、本当にそうなのかソースコードと実機で検証しました。
HTTPに詳しい方には当たり前の内容かもしれませんが、自分はまだ学び始めたばかりで気になったので調べてみた記録です。
公式ドキュメントの記載
Next.js公式ドキュメント(v16.2.6時点)のRoute Handlersのページには、以下のようにOPTIONSの自動実装のみが明記されています。
// If `OPTIONS` is not defined, Next.js will automatically implement `OPTIONS`
// and set the appropriate Response `Allow` header depending on the other
// methods defined in the Route Handler.
export async function OPTIONS(request: Request) {}
HEADについては、サポートされるメソッド一覧に export async function HEAD(request: Request) {} が並んでいるだけで、未定義時の挙動には触れられていません。
ソースコードの確認
Next.jsのソースコード auto-implement-methods.ts を確認しました。
const AUTOMATIC_ROUTE_METHODS = ['HEAD', 'OPTIONS'] as const
export function autoImplementMethods(
handlers: AppRouteHandlers
): Record<HTTP_METHOD, AppRouteHandlerFn> {
// ...省略...
for (const method of missing) {
if (method === 'HEAD') {
if (handlers.GET) {
methods.HEAD = handlers.GET // GETハンドラをそのままHEADに代入
implemented.add('HEAD')
}
continue
}
if (method === 'OPTIONS') {
// ...OPTIONSの自動実装(省略)...
}
}
return methods
}
ポイントは以下の3点です。
-
AUTOMATIC_ROUTE_METHODSにHEADとOPTIONSの両方が含まれています - HEADが未定義かつGETが定義されている場合、
methods.HEAD = handlers.GETでGETのハンドラがそのまま代入されます - ボディの除去はNext.jsの責務ではなく、HTTPプロトコル層(Node.jsのhttp実装)が行います
実機検証
テストコード
GETのみを定義したRoute Handlerを用意しました。
// app/api/test/route.ts
export async function GET() {
return Response.json(
{ message: "Hello from GET", timestamp: Date.now() },
{
headers: {
"X-Custom-Header": "test-value",
"Last-Modified": new Date().toUTCString(),
},
}
);
}
検証結果(Next.js 16.2.6)
# GET — 通常通り200 + ボディ
$ curl -D- http://localhost:3999/api/test
HTTP/1.1 200 OK
content-type: application/json
last-modified: Sat, 23 May 2026 12:08:39 GMT
x-custom-header: test-value
{"message":"Hello from GET","timestamp":1779538119046}
# HEAD — 200 + ヘッダーのみ(ボディなし)
$ curl -D- -X HEAD http://localhost:3999/api/test
HTTP/1.1 200 OK
content-type: application/json
last-modified: Sat, 23 May 2026 12:08:39 GMT
x-custom-header: test-value
# POST — 405(未定義のメソッド)
$ curl -D- -X POST http://localhost:3999/api/test
HTTP/1.1 405 Method Not Allowed
# OPTIONS — 204 + Allowヘッダー(自動実装)
$ curl -D- -X OPTIONS http://localhost:3999/api/test
HTTP/1.1 204 No Content
allow: GET, HEAD, OPTIONS
| メソッド | ステータス | ボディ | カスタムヘッダー |
|---|---|---|---|
| GET | 200 | あり(JSON) | あり |
| HEAD | 200 | なし(自動除去) | あり(GETと同じ) |
| POST | 405 | なし | — |
| OPTIONS | 204 | なし | Allow: GET, HEAD, OPTIONS |
なぜボディが消えるのか
methods.HEAD = handlers.GET なので、HEADリクエストでもGETと同じ処理が実行され、Response.json(...) でボディ付きのレスポンスが生成されます。
HEADリクエストに対するレスポンスにボディを含めてはならないため、Node.jsのHTTPサーバー実装がこの仕様に従い、HEADレスポンスからボディを自動的に除去します。
具体的には、Node.jsの _http_server.js で ServerResponse の生成時にHEADかどうかを判定しています。
// node/lib/_http_server.js L203
function ServerResponse(req, options) {
OutgoingMessage.call(this, options);
if (req.method === 'HEAD') this._hasBody = false;
// ...
}
そして _http_outgoing.js の write() で _hasBody が false の場合、ボディの書き込みを無視します。
// node/lib/_http_outgoing.js L942
if (!msg._hasBody) {
if (msg[kRejectNonStandardBodyWrites]) {
throw new ERR_HTTP_BODY_NOT_ALLOWED();
} else {
debug('This type of response MUST NOT have a body. ' +
'Ignoring write() calls.');
process.nextTick(callback);
return true;
}
}
つまり、Next.jsはGETのハンドラをそのまま流用するだけで、ボディ除去はフレームワークではなくNode.jsのHTTP層が _hasBody フラグで制御しています。
Honoとの比較
Honoは公式ドキュメントでHEADの自動処理を明記しています。
| フレームワーク | HEAD自動実装 | ドキュメント記載 |
|---|---|---|
| Next.js(App Router) | する(GETから自動実装) | なし |
| Hono | する(GETのボディなし版として処理) | あり |
まとめ
- Next.jsはHEADをGETから自動実装します。明示的に定義しなくても動きます
- 実装の根拠は
auto-implement-methods.tsのmethods.HEAD = handlers.GETです - ボディ除去はHTTPプロトコル層(Node.js)の責務であり、Next.jsは関与していません
- 細かい部分ですが確かめてみると非常に勉強になりました