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?

Next.js の `route.ts` で起こっていることメモ

Posted at

UI(page.tsx)→ route.ts → Django(views.py) → route.ts → UI というリクエスト往復の流れ

前提・構成

  • フロント:Next.js App Router(app/ 配下)
  • API 入口:app/api/summarize/route.ts
  • バックエンド:Django(/summarizer/summarize/views.py でエンドポイント)
  • 通信:UI は 常に Next.js の API Route(同一オリジン /api/...)に向け、route.ts が Django に“中継”する設計

全体のシーケンス

  • route.tsプロキシ(ゲートウェイ) として振る舞う。
  • セキュリティ(バックエンドURLの秘匿)、エラーフォーマットの統一、将来の差し替えに強い。

route.ts(実コード+行ごとの注釈)

import { NextResponse } from "next/server"; 
// Next.js(App Router)のレスポンス生成ユーティリティ

export async function POST(req: Request) { 
// この API ルートが受け取る POST メソッドのハンドラ

  const { text } = await req.json(); 
  // フロントから届いた JSON 本文をパースし、text プロパティだけを分割代入

  try {
    const res = await fetch("http://localhost:8000/summarizer/summarize/", {
      method: "POST",                               // サーバーに JSON を送るので POST を明示
      headers: { "Content-Type": "application/json" }, // 本文が JSON であることを宣言
      body: JSON.stringify({ text }),               // JSオブジェクト → JSON文字列へ変換して送信
    });

    if (!res.ok) {
      const errorText = await res.text();           // HTML等のエラーボディをテキストで取得
      return NextResponse.json(                     // フロント向けに「常にJSON」で返す
        { error: errorText }, 
        { status: res.status }
      );
    }

    const data = await res.json();                  // Django からの JSON をオブジェクト化
    return NextResponse.json({ summary: data.summary }); 
    // 必要なフィールドだけに整形して返し直す(境界で正規化)
  } catch (err) {
    return NextResponse.json({ error: String(err) }, { status: 500 }); 
    // 例外は JSON で統一して返す(UI 側の扱いが簡単になる)
  }
}

ポイント

  • 「二重にJSON化している」わけではない

    • Django の JsonResponseDJ → route.ts の HTTP レスポンス。
    • NextResponse.json(...)route.ts → ブラウザ への新しい HTTP レスポンス。
    • 異なるレイヤの責務なので、route.ts で“返し直す”のは妥当である。
  • ここで フィールドを絞る・ステータスやヘッダを調整 できるのが利点。


UI 側(page.tsx)の最低限

// 送信: body は必ず JSON.stringify する・POST を明示する・Content-Type を付ける
await fetch("/api/summarize", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ text }),
});

// 受信: JSON を取り出して描画
const data = await res.json();     // => { summary: "..." }
setSummary(data.summary);
  • JSON.stringify を忘れると、[object Object] などの無効な本文になり、サーバー側で JSON として読めず失敗する。
  • method を省くとデフォルトは GET であり、body を付けても意味をなさないので 必ず POST を指定する。

バックエンド(Django views.py)の最小実装

from django.http import JsonResponse, HttpResponseNotAllowed
from django.views.decorators.csrf import csrf_exempt
import json
from .news_summarizer_model import run_summary

@csrf_exempt
def summarize(request):
    if request.method == "POST":
        data = json.loads(request.body)  # Content-Type: application/json 前提で受信
        text = data.get("text")
        summary = run_summary(text)      # 要約処理
        return JsonResponse({"summary": summary})  # JSONで返却
    return HttpResponseNotAllowed(["POST"], "Use POST method instead")
  • App Router から別オリジンに飛ばす設計では、簡易対応として @csrf_exempt を付けることが多い(本番運用ではCSRF/CORSや認証を検討する)。

分割代入(const { text } = await req.json())の意味

  • await req.json(){ text: "..." } という JSオブジェクトを返す。
  • 分割代入で text だけを取り出すと、以後 data.text と書かずに済む。
  • どのプロパティを使うのかが明示でき、可読性・保守性が上がる。
// 複数取りたい場合
const { text, mode = "medium", lang } = await req.json();

なぜ Content-Type: application/json が必須か

  • サーバーが 本文の形式を判別するためである。
  • これがないと Django 側は JSON と認識できず、json.loads や DRF の JSON パーサーが正しく動かない。
  • フォームなら application/x-www-form-urlencoded、ファイルなら multipart/form-data を指定するのと同じ話である。

よくある落とし穴

  • method を書かない → デフォルトは GET。body を付けても無効。
  • JSON.stringify を忘れる → サーバーには "[object Object]" が届いて JSON として読めない。
  • バックエンドの生エラーページ(HTML) → そのまま返すと UI 側で扱いにくい。route.tsJSON に整形して返すと良い。
  • CORS/CSRF → 開発中は緩めても、本番では設計が必要(API ゲートウェイ方式はここを隠蔽しやすい)。

発展:環境変数化と“パススルー”実装

バックエンドURLを環境変数化(例)

const BACKEND_URL = process.env.NEXT_PUBLIC_API_BASE ?? "http://localhost:8000";
const res = await fetch(`${BACKEND_URL}/summarizer/summarize/`, { ... });
  • ビルド時に環境ごと(開発・本番)で切替可能になる。

ほぼ“そのまま返す”パススルー例

const res = await fetch(`${BACKEND_URL}/summarizer/summarize/`, { method: "POST", headers, body });
if (!res.ok) {
  const text = await res.text();
  return NextResponse.json({ error: text }, { status: res.status });
}
const data = await res.json(); // 形を変えず UI へ
return NextResponse.json(data, { status: res.status });
  • 今回のように フィールドを最小化したいなら { summary: data.summary } に整形して返すのが明快である。

まとめチェックリスト

  • UI → fetchmethod: "POST" 明示Content-Type: "application/json"JSON.stringify を必ず付ける
  • route.tsバックエンドへの中継点として、エラー整形レスポンス正規化を行う
  • NextResponse.json(...)フロント向けの新しいHTTPレスポンスを作る処理(“二重JSON化”ではない)
  • Django は JsonResponse で返す(App Router 側で JSON として受けやすい)
  • バックエンドURLは 環境変数化、将来の差し替えに備える
  • (将来)認証・CORS/CSRF 方針は route.ts 境界で統一して扱うと管理が楽になる
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?