[Astro #105] Next.jsからAstro(WSL2)移植時にハマったAPI Routeの404/500エラーとLM Studio(ローカルLLM)連携の全手順

[Astro #105] Next.jsからAstro(WSL2)移植時にハマったAPI Routeの404/500エラーとLM Studio(ローカルLLM)連携の全手順

はじめに

前回の記事[Lain Clock カレンダー機能の WebGL 3D メッシュ化と描画順序の最適化]に続き、今回は Lain Clock に搭載している ローカルLLM(LM Studio)を用いた 3D キャラクター対話機能 の移設作業についてまとめます。

元々 Next.js (App Router) 上で動いていたチャット機能を Astro プロジェクト(Astro + Three.js)に移植した際、「API エンドポイントが見つからない 404 エラー」「引数の差異による 500 エラー」、そして 「WSL2 ⇔ Windows 間のネットワーク疎通エラー (fetch failed)」 という 3 段階の壁にぶつかりました。

同じように「WSL2 上で Astro を動かし、Windows 上の LM Studio と連携したい」という方の参考になれば幸いです。

発生したトラブルの概要

Next.js からフロントエンドコード(chat.js 等)だけを移植して実行したところ、以下のエラーが順次発生しました。

  1. 404 Not Found: /api/chat にリクエストを送るも Astro の標準 404 ページが返る。
  2. 500 Internal Server Error (req.json is not a function): API ファイル作成後、リクエストボディの解析で失敗。
  3. 500 Internal Server Error (fetch failed): API 内から LM Studio (http://127.0.0.1:1234) への fetch が接続拒否される。

解決策 1: Astro における API エンドポイントの作成ルール

① ディレクトリ構造とファイル名

Next.js (App Router) では app/api/chat/route.js と命名しますが、Astro では ファイル名自体がパス になります。

  • Next.js: app/api/chat/route.js/api/chat
  • Astro: src/pages/api/chat.js/api/chat

public/clock/ などの静的ディレクトリではなく、必ず src/pages/api/ 配下に配置する必要があります。

export const prerender = false; の明記

Astro はデフォルトで SSG(静的サイト生成)モードとして動作します。動的な POST リクエストを受け付けるため、ファイルの最上部に以下を記述します。

export const prerender = false;

③ POST 関数の引数(Request オブジェクト)の受け取り方

Next.js では export async function POST(req)req.json() を直接呼べますが、Astro の API エンドポイント関数に渡されるのは APIContext オブジェクト です。

そのため、分割代入で { request } を取得して .json() を呼び出します。

// ❌ Next.js スタイル(Astroでは req.json is not a function になる)
export async function POST(req) {
  const body = await req.json();
}

// ⭕ Astro スタイル
export async function POST({ request }) {
  const body = await request.json();
}

解決策 2: WSL2 ➔ Windows (LM Studio) 間のネットワーク疎通

Astro 側の API エンドポイントが正常に動作し始めても、fetch failed (500) が発生しました。

原因:仮想化ネットワークの境界

  • Astro サーバー: WSL2 (Ubuntu) 上で動作
  • LM Studio: Windows ホスト上で動作

WSL2 内から 127.0.0.1:1234 へアクセスしても、それは WSL2 内部のループバック を指してしまい、Windows 上で起動している LM Studio には届きません。

対処 1: LM Studio の「Serve on Local Network」を有効化

デフォルト状態の LM Studio は Windows 内(127.0.0.1)からのアクセスしか許可していません。

LM Studio の Server Settings を開き、Serve on Local NetworkON に変更します(必要に応じて Enable CORS も ON)。

スクショ:

modelを選択

[Astro #105] Next.jsからAstro(WSL2)移植時にハマったAPI Routeの404/500エラーとLM Studio(ローカルLLM)連携の全手順

modelが選択されロードが終わるまで待つ

[Astro #105] Next.jsからAstro(WSL2)移植時にハマったAPI Routeの404/500エラーとLM Studio(ローカルLLM)連携の全手順

LM Studio の Server Settings を開き、Serve on Local Network を ON に変更

[Astro #105] Next.jsからAstro(WSL2)移植時にハマったAPI Routeの404/500エラーとLM Studio(ローカルLLM)連携の全手順

LM Studio の Server Settings で、CORS を ON に変更

[Astro #105] Next.jsからAstro(WSL2)移植時にハマったAPI Routeの404/500エラーとLM Studio(ローカルLLM)連携の全手順

対処 2: 接続先 IP アドレスの指定

WSL2 から Windows ホストを指す IP アドレス(LAN IP や WSL2 のデフォルトゲートウェイ IP)を指定します。

# WSL2 ターミナルで Windows 側の IP を確認
ip route show default | awk '{print $3}'
# 例: 172.23.208.1 や LAN 内 IP (192.168.0.5)

WSL2 ターミナルから curl でモデル一覧が取得できるか疎通確認を行います。

curl [http://192.168.0.5:1234/v1/models](http://192.168.0.5:1234/v1/models)

正常にレスポンス(JSON)が返ってくれば、ネットワーク開通です。

$ curl http://192.168.0.5:1234/v1/models
{
  "data": [
    {
      "id": "qwen/qwen3.5-9b",
      "object": "model",
      "owned_by": "organization_owner"
    },
    {
      "id": "openai/gpt-oss-20b",
      "object": "model",
      "owned_by": "organization_owner"
    },
    {
      "id": "qwen/qwen3.5-35b-a3b",
      "object": "model",
      "owned_by": "organization_owner"
    },
    {
      "id": "text-embedding-nomic-embed-text-v1.5",
      "object": "model",
      "owned_by": "organization_owner"
    }
  ],
  "object": "list"

完成した API コード (src/pages/api/chat.js)

最終的に整備した Astro 側の API エンドポイントコードは以下の通りです。

// src/pages/api/chat.js
export const prerender = false;

// Windows ホストの IP アドレスと LM Studio のポート番号
const LMSTUDIO_BASE_URL = "[http://192.168.0.5:1234/v1](http://192.168.0.5:1234/v1)";
const LMSTUDIO_MODEL = "qwen/qwen3.5-9b";

const systemPrompt = `
あなたはMMD時計アプリ内の小さな案内キャラクターです。
必ず日本語で答えてください。
短く自然な会話文だけを返してください。
思考過程や解説は書かず、ユーザーに見せる返答だけを1文で返してください。
`;

export async function POST({ request }) {
  try {
    const body = await request.json();
    const userText = String(body?.message || "").trim();
    const model = String(body?.model || LMSTUDIO_MODEL);

    if (!userText) {
      return Response.json({ error: "message is required" }, { status: 400 });
    }

    const lmRes = await fetch(`${LMSTUDIO_BASE_URL}/chat/completions`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        model: model,
        messages: [
          { role: "system", content: systemPrompt },
          { role: "user", content: userText },
        ],
        temperature: 0.7,
        max_tokens: 120,
      }),
    });

    if (!lmRes.ok) {
      const errorText = await lmRes.text().catch(() => "");
      return Response.json(
        {
          error: "LM Studio API failed",
          status: lmRes.status,
          details: errorText,
        },
        { status: 500 }
      );
    }

    const data = await lmRes.json();
    const reply =
      data?.choices?.[0]?.message?.content?.trim() || "うまく返答できませんでした。";

    return Response.json({ reply });
  } catch (err) {
    return Response.json(
      {
        error: "Internal server error",
        details: err instanceof Error ? err.message : String(err),
      },
      { status: 500 }
    );
  }
}

動作確認

設定完了後、LM Studio の Developer Logs にリクエストが記録され、推理結果(Thinking)を経て回答が生成されていることが確認できます。

フロントエンド側(Lain Clock)から「おはよう」と送信すると、LM Studio (Qwen 3.5 9B) からの応答メッセージが吹き出しに反映され、キャラ対話機能が完全復旧しました。

[Astro #105] Next.jsからAstro(WSL2)移植時にハマったAPI Routeの404/500エラーとLM Studio(ローカルLLM)連携の全手順 [Astro #105] Next.jsからAstro(WSL2)移植時にハマったAPI Routeの404/500エラーとLM Studio(ローカルLLM)連携の全手順

まとめ

Next.js から Astro への移行における API 構築、および WSL2 開発環境でのローカル LLM 連携時のポイントは以下の 3 点です。

  1. Astro の API エンドポイントsrc/pages/api/ 直下にファイルを作成し、export const prerender = false; を付与する。
  2. コンテキストの引数POST({ request }) のように波括弧で取得する。
  3. WSL2 から Windows の LM Studio へ接続する際は、LM Studio 側で Serve on Local Network を有効化し、127.0.0.1 ではなく Windows 側の IP アドレスを指定する。

これで、完全ローカル環境下(プライバシー安全)での WebGL 3D アプリ × ローカル LLM 対話システムの構築基盤が整いました!