0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

備忘録:NextResponse と Response の body(ReadableStream)の仕組み

Posted at

Next.js の middleware や API Route でレスポンスにヘッダーや Cookie を付与したいとき、素の Response を展開してはいけません。
理由はシンプルで、Response の body が ReadableStream だからです。

Response の body = ReadableStream

fetch が返す Response オブジェクトの body プロパティは、単なる文字列や JSON ではなく ReadableStream になっています。

const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
console.log(res.body); 
// → ReadableStream { locked: false }

ストリームは一度しか読めない

ReadableStream は「順番に流れてくるデータ」を扱う仕組みです。
一度読み出すと 消費されて空になるため、再利用はできません。

const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");

const text1 = await res.text(); // 1回目はOK
const text2 = await res.text(); // ❌ 空っぽになる or エラー

内部で何が起きているのか

ReadableStream には以下の状態があります:

  • queue : ネットワークから届いたデータの一時置き場
  • locked : 読み取り中かどうか(getReader() で true)
  • closed : 全部読み終わった状態(再オープン不可)

res.text()res.json() を呼ぶと:

  1. queue に溜まったチャンクを順番に消費
  2. 読み取り中は locked = true
  3. 最後まで読むと closed → 以降は再利用不可

このようにReadableStreamは「ライフサイクル」のような性質を持つオブジェクトで、
それがfetch後のレスポンスボディの構造となっています。

検証コードで確認

const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const body = res.body;

console.log("読み取り前:", body.locked); // false

const reader = body.getReader();
console.log("getReader後:", body.locked); // true

while (!(await reader.read()).done) {}
console.log("ストリーム読み切った");

const { done } = await reader.read();
console.log("done:", done); // true → closed 状態

読み取り開始で locked = true、最後まで読んだら done = true (closed) になるのが確認できる。


Next.js での落とし穴

middleware で Response をそのまま返すとき、もし先に body を展開すると空のレスポンスしか返りません。

export async function middleware(req: NextRequest) {
  const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");

  const data = await res.json(); // ← body を消費
  return res; // ❌ クライアントには空
}

解決策:NextResponse を使う

NextResponse.json() なら、データをシリアライズして 新しいレスポンスを生成できるため問題ありません。

import { NextResponse } from "next/server";

export async function middleware(req: NextRequest) {
  const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
  const data = await res.json(); // ここで1回だけ読む

  return NextResponse.json(data); // ✅ 新しい Response を返す
}

まとめ

  • Response の body は ReadableStream(消費型)
  • 一度読み始めると locked → closed になり、再利用できない
  • middleware で Response を直接返すと空になる落とし穴がある
  • そのため Next.js では NextResponse を使って新しいレスポンスを作り直すのが正解
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?