[Astro #105] Next.jsからAstro(WSL2)移植時にハマったAPI Routeの404/500エラーとLM Studio(ローカルLLM)連携の全手順
はじめに
前回の記事[Lain Clock カレンダー機能の WebGL 3D メッシュ化と描画順序の最適化]に続き、今回は Lain Clock に搭載している ローカルLLM(LM Studio)を用いた 3D キャラクター対話機能 の移設作業についてまとめます。
[JavaScript] Lain Clock カレンダー機能の WebGL 3D メッシュ化と描画順序(renderOrder/depthWrite)の最適化 // PROTOCOL.LAIN
WebGL空間に統合された3Dカレンダーモジュールの実装解説。Off-screen Canvasを用いた動的テクスチャ生成、transparentマテリアルにおけるdepthWrite: falseとrenderOrderによる重なり順の制御、設定保存・復元との透過的連携をまとめています。
lain-lab.com元々 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 等)だけを移植して実行したところ、以下のエラーが順次発生しました。
- 404 Not Found:
/api/chatにリクエストを送るも Astro の標準 404 ページが返る。 - 500 Internal Server Error (
req.json is not a function): API ファイル作成後、リクエストボディの解析で失敗。 - 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 Network を ON に変更します(必要に応じて Enable CORS も ON)。
スクショ:
modelを選択
modelが選択されロードが終わるまで待つ
LM Studio の Server Settings を開き、Serve on Local Network を ON に変更
LM Studio の Server Settings で、CORS を ON に変更
対処 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) からの応答メッセージが吹き出しに反映され、キャラ対話機能が完全復旧しました。
まとめ
Next.js から Astro への移行における API 構築、および WSL2 開発環境でのローカル LLM 連携時のポイントは以下の 3 点です。
- Astro の API エンドポイントは
src/pages/api/直下にファイルを作成し、export const prerender = false;を付与する。 - コンテキストの引数は
POST({ request })のように波括弧で取得する。 - WSL2 から Windows の LM Studio へ接続する際は、LM Studio 側で
Serve on Local Networkを有効化し、127.0.0.1ではなく Windows 側の IP アドレスを指定する。
これで、完全ローカル環境下(プライバシー安全)での WebGL 3D アプリ × ローカル LLM 対話システムの構築基盤が整いました!