Next.js 読解メモ 05: 外部API・秘匿情報・API Route最小プロキシ
· 3 min read
判断基準(30秒)
- クライアント直叩きOK
キー不要 or 完全公開可キー(NEXT_PUBLIC_)、CORS許可、誤用リスク小。 - 直叩きNG → /api 経由
秘密キー/Bearerが必要、署名生成が必要、CORSでブロック、レート制御したい。
クライアント直叩きの最小形(公開キーのみ)
"use client";
import { useEffect, useState } from "react";
export default function List() {
const [data, setData] = useState<any[] | null>(null);
useEffect(() => {
const ac = new AbortController();
fetch(`${process.env.NEXT_PUBLIC_API_BASE}/items`, { signal: ac.signal })
.then(r => r.json()).then(setData).catch(() => {});
return () => ac.abort();
}, []);
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
NEXT_PUBLIC_の値は誰でも見える。秘密は入れない。
最小プロキシ(App Router / Vercel)
// app/api/search/route.ts
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const q = searchParams.get("q") ?? "";
const upstream = await fetch(
`https://third.api.example.com/search?q=${encodeURIComponent(q)}`,
{
headers: { Authorization: `Bearer ${process.env.THIRD_API_KEY!}` },
cache: "no-store", // 上流は毎回取得(状態に応じて調整)
}
);
if (!upstream.ok) {
return Response.json({ error: "upstream error" }, { status: upstream.status });
}
const json = await upstream.json();
// 共有キャッシュ(CDN)での最適化:1分保持、10分はstaleでも即応→裏で再検証
return new Response(JSON.stringify(json), {
headers: {
"Content-Type": "application/json",
"Cache-Control": "s-maxage=60, stale-while-revalidate=600",
},
});
}
- クライアントは 同一オリジンの
/api/search?q=...を叩くだけ → CORS問題も回避。 - 鍵は Vercel の Environment Variables に置き、サーバ側(API Route)だけで読む。
環境変数の鉄則(30秒)
- 公開:
NEXT_PUBLIC_XXX(露出OKだけ) - 秘密:
process.env.SECRET(API Route / サーバコンポーネント内のみ) - 環境ごとに分離:Production / Preview / Development で別値
よくある落とし穴(30秒)
- 秘密キーを
NEXT_PUBLIC_に入れる(即流出) - 外部画像やSDKのCORSで詰まる → /api でプロキシ or 相手側の設定確認
- レート制限なしで連打 → Upstash等で簡易Rate LimitをAPI Routeに
仕上げチェック(10秒)
- Bearerキー必須の外部API:/api を挟む
- CORSでブロック:同一オリジンの /api に逃がす
- ブログの公開設定:秘密はENV、クライアントには渡さない
以上でこの短期シリーズはおしまいです。必要になったら「画像/フォント最適化」「パフォーマンス計測」「動的ルートの静的化テク」版も用意できます。