こんにちは、かつコーチです。
前回は、revalidatePathを呼んでも画面が更新されなかった原因と、router.refresh()やrevalidateTagによる対処法を解説しました。
N11〜N14でServer/Client Componentsとデータ連携の応用を一通り見てきましたが、最後にもう1つ、私が実際にハマった環境変数の話をします。
「動くはずのコードがなぜかundefinedになる」という、地味だけど初中級者がよく踏む落とし穴です。
何が起きるか
Next.jsにおける環境変数の基本
Next.jsでは、プロジェクトルートの.env.localなどに環境変数を定義し、コード内でprocess.env.変数名として参照します。
# .env.local
API_SECRET_KEY=super-secret-value
DATABASE_URL=postgres://...
// Server Componentやサーバー専用コードの中
const secret = process.env.API_SECRET_KEY;
Server Component、Server Actions、Route Handlersのようなサーバー側のコードからは、これで問題なく値を取得できます。
サーバー専用とクライアント公開の境界
ここで重要なのが、Next.jsの環境変数はデフォルトでサーバー専用という仕様です。
NEXT_PUBLIC_という接頭辞(プレフィックス)を付けない限り、その環境変数はビルド時にクライアント側のJavaScriptバンドルに埋め込まれません。
これはセキュリティ上の設計であり、APIキーやDB接続情報のような秘密情報がうっかりブラウザに漏れるのを防ぐための仕組みです。
逆に言えば、ブラウザ側(Client Component)から参照したい値には、明示的にNEXT_PUBLIC_を付ける必要があります。
基本の書き方
手順1:サーバー専用の環境変数を使う
# .env.local
STRIPE_SECRET_KEY=sk_live_xxxxxxxx
// app/api/checkout/route.ts(Route Handler、サーバー側)
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST() {
const session = await stripe.checkout.sessions.create({
// ...
});
return Response.json({ url: session.url });
}
STRIPE_SECRET_KEYのような秘密鍵は、NEXT_PUBLIC_を付けずサーバー側だけで扱うのが原則です。
手順2:クライアントに公開してよい値にNEXTPUBLICを付ける
# .env.local
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_xxxxxxxx
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXX
// app/components/PaymentButton.tsx(Client Component)
'use client';
import { loadStripe } from "@stripe/stripe-js";
const stripePromise = loadStripe(
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!
);
export function PaymentButton() {
// ...
return <button>支払いへ進む</button>;
}
公開鍵(publishable key)のように「そもそもブラウザ側に見えても問題ない値」にはNEXT_PUBLIC_を付け、Client Componentから直接参照します。
手順3:型安全に環境変数を扱う
環境変数はデフォルトではstring | undefined型になるため、存在チェックを共通化しておくと安全です。
// lib/env.ts
function getEnv(key: string): string {
const value = process.env[key];
if (!value) {
throw new Error(`環境変数 ${key} が設定されていません`);
}
return value;
}
export const env = {
stripeSecretKey: getEnv("STRIPE_SECRET_KEY"),
publicStripeKey: getEnv("NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY"),
};
起動時に環境変数の設定漏れを検知できるようになり、実行中に突然undefinedエラーが出るリスクを減らせます。
つまずきポイント・一次情報の体験談
実際に起きたこと
私が地図表示機能を実装していたときのことです。
地図APIのキーを.env.localに設定し、Client Componentから読み込むコードを書きました。
❌ Before:NEXTPUBLICを付け忘れたまま参照する
# .env.local
MAP_API_KEY=abcdefg12345
// app/components/MapView.tsx
'use client';
import { useEffect, useRef } from "react";
export function MapView() {
const apiKey = process.env.MAP_API_KEY;
useEffect(() => {
console.log("APIキー:", apiKey); // undefinedが表示される
// 地図SDKの初期化処理(apiKeyを使う)
}, [apiKey]);
return <div id="map" />;
}
ローカル環境で動作確認したところ、コンソールにAPIキー: undefinedと表示され、地図が真っ白のまま表示されませんでした。
原因の調査
最初は.env.localの記述ミスや、開発サーバーの再起動忘れを疑い、何度もファイルを見直しました。
.env.localの内容自体は正しく、サーバー側のコード(app/api/配下など)から同じMAP_API_KEYを参照すると、値は問題なく取得できていました。
「サーバー側では取れるのに、クライアント側では取れない」という違いから、Next.jsの環境変数がデフォルトでサーバー専用であることをようやく思い出しました。
Client Componentは最終的にブラウザで実行されるコードなので、NEXT_PUBLIC_が付いていない環境変数はビルド時にバンドルへ埋め込まれず、process.env.MAP_API_KEYは常にundefinedになっていたのです。
✅ After:NEXTPUBLICを付けて公開する
# .env.local
NEXT_PUBLIC_MAP_API_KEY=abcdefg12345
// app/components/MapView.tsx
'use client';
import { useEffect } from "react";
export function MapView() {
const apiKey = process.env.NEXT_PUBLIC_MAP_API_KEY;
useEffect(() => {
console.log("APIキー:", apiKey); // 正しく値が入る
// 地図SDKの初期化処理
}, [apiKey]);
return <div id="map" />;
}
環境変数名の先頭にNEXT_PUBLIC_を付け、開発サーバーを再起動したところ、地図が正常に表示されるようになりました。
注意点:公開してよい値かどうかは必ず精査する
このとき同時に強く意識するようになったのが、「NEXT_PUBLIC_を付ければ動くから」といって安易に秘密情報へ付けてはいけない、ということです。
NEXT_PUBLIC_を付けた値はビルド後のJavaScriptファイルに平文で埋め込まれ、ブラウザの開発者ツールから誰でも閲覧できてしまいます。
私が扱っていた地図APIキーはドメイン制限をかけて公開前提で発行されたキーだったため問題ありませんでしたが、DBの接続情報やDB操作用のシークレットキーには絶対に付けてはいけません。
以降、環境変数を追加するたびに「この値はブラウザに見えても問題ないか」を必ず自問してからNEXT_PUBLIC_の要否を判断するようにしています。
まとめ
この記事のポイント
- Next.jsの環境変数はデフォルトでサーバー専用で、Client Componentからは参照できない
- ブラウザに公開してよい値だけに
NEXT_PUBLIC_接頭辞を付け、クライアント側から参照できるようにする NEXT_PUBLIC_を付けた値はビルド後のJSに平文で埋め込まれるため、秘密情報には絶対に付けない- 「サーバー側では値が取れるのにクライアント側では取れない」ときは、まず
NEXT_PUBLIC_の付け忘れを疑う - 環境変数を共通関数で取得し、未設定時にエラーを投げるようにしておくと設定漏れに早く気づける
次に読むべき記事
N11〜N15でServer/Client Componentsの応用とデータ連携・キャッシュの基本を一通り解説してきました。
次回からは認証まわりのテーマに入ります。
次回は、Auth.js(NextAuth)で認証機能を実装する方法を解説します。
→ 次の記事:Auth.js(NextAuth)で認証機能を実装する