概要
前回作成した React Router v7(フレームワークモード)アプリケーションのサーバサイドで.envファイルを読み込む実装を追加します。
Vite のビルドシステムでは、VITE_ プレフィックスがついた環境変数のみがクライアントサイドに公開されます。しかし、API の URL やデバッグ用のユーザ名など、サーバサイド専用の環境変数は公開したくありません。そこで、通常の Node.js アプリケーションと同様に dotenv を使用して、サーバサイドで.envファイルを読み込む方法を実装します。
フォルダ構成
開発環境
app/
├── routes/
│ ├── _auth.tsx (レイアウト、API呼び出しあり)
│ └── home.tsx (ページ)
├── utilities/
│ ├── env.ts (環境変数管理)
│ └── axios.ts (Axiosインスタンス管理)
├── root.tsx (アプリケーションエントリーポイント)
└── routes.ts
サーバ
rr/
├── build/
│ ├── client/
│ └── server/
├── node_modules/
├── .env (これを追加)
├── package.json
└── web.config
dotenv のインストール
まず、dotenvパッケージをインストールします。
※サーバ上でも同様です。
pnpm install dotenv
または
npm install dotenv
.env ファイルの作成
サーバ上のプロジェクトフォルダに.envファイルを作成します。
また、開発環境の.envにも同様の内容を追加します。
API_URL=http://localhost:8080/api/
DEBUG_USER=domain\sampleuser
重要: .envファイルは機密情報を含むため、必ず.gitignoreに追加してください。
env.ts
環境変数を管理するユーティリティファイルです。
このモジュールは、サーバサイドでdotenvを使用して.envファイルから環境変数を読み込み、型安全にアクセスできるようにします。
※ファイル名はここで指定しているので、ファイル名は.envでなくても構いません。
重要なポイント:
-
getEnv()は初回呼び出し時に自動的に.envファイルを読み込む - 2 回目以降は初期化処理をスキップ(パフォーマンス対策)
-
EnvKey型で利用可能な環境変数キーを制限(型安全性の確保)
/**
* 環境変数管理ユーティリティ
* サーバーサイドでdotenvを読み込み、環境変数を提供
*/
import { config } from "dotenv";
let isInitialized = false;
type EnvKey = "API_URL" | "DEBUG_USER";
/**
* 環境変数を初期化(dotenvを読み込み)
* getEnv()から自動的に呼ばれるため、通常は直接呼び出す必要はありません
*/
function initEnv() {
if (isInitialized) return;
config({ path: ".env" });
isInitialized = true;
console.log("Environment variables loaded from .env");
}
/**
* 環境変数を取得
* 初回呼び出し時に自動的に.envファイルを読み込みます
*/
export function getEnv(key: EnvKey): string | undefined {
if (!isInitialized) {
initEnv();
}
return process.env[key];
}
root.tsx
root.tsx の middleware で環境変数を使用します。getEnv()は初回呼び出し時に自動的に.envファイルを読み込むため、明示的な初期化は不要です。
middleware の処理フロー:
- 開発環境であれば、
.envのDEBUG_USERを使用(getEnv()が自動的に.envを読み込む)
import { createContext } from "react-router";
import type { Route } from "./+types/root";
import { getEnv } from "./utilities/env";
// middleware用のコンテキスト作成
export const rootContext = createContext<AuthenticatedUser>();
/**
* windows認証ミドルウェア
* ユーザー名・ドメイン名を取得します
*/
const rootMiddleware: Route.MiddlewareFunction = async ({ request, context }) => {
try {
// DEV環境なら固定のユーザーを返す
if (typeof process !== "undefined" && process.env.NODE_ENV === "development") {
const [domain, userName] = (getEnv("DEBUG_USER") ?? "unknown").split("\\");
context.set(rootContext, { userName, domain });
return;
}
// ...
}
};
export const middleware: Route.MiddlewareFunction[] = [rootMiddleware];
// Layout、App、ErrorBoundaryなどのコンポーネント...
axios.ts
Axios インスタンスを管理するユーティリティファイルです。
環境変数から API の URL を取得し、Axios のベース URL として設定します。これにより、各 API 呼び出し時に完全な URL を指定する必要がなくなります。
重要なポイント:
-
getAxios()は初回呼び出し時に自動的に初期化され、Axios インスタンスをシングルトンとして管理 - 環境変数
API_URLから自動的にベース URL を設定 -
API_URLが未設定の場合はエラーを投げる(設定忘れを防ぐ)
import axios, { type AxiosInstance } from "axios";
import { getEnv } from "./env";
let axiosInstance: AxiosInstance | null = null;
/**
* Axiosインスタンスを取得
* 未初期化の場合は自動的に初期化(環境変数からAPI URLを設定)
*/
export function getAxios(): AxiosInstance {
if (!axiosInstance) {
const baseURL = getEnv("API_URL");
if (!baseURL) {
throw new Error("API_URL environment variable is not set");
}
axiosInstance = axios.create({
baseURL,
headers: { "Content-Type": "application/json" },
});
console.log("Axios initialized with baseURL:", axiosInstance.defaults.baseURL);
}
return axiosInstance;
}
_auth.tsx
_auth.tsxの middleware で、Axios を使用して API からユーザ情報を取得します。
処理の流れ:
-
rootContextから認証済みユーザ情報を取得 - ユーザ名が空または未定義の場合はエラーページにリダイレクト
-
getAxios()を使用して API からユーザ詳細情報を取得 - API 呼び出しが失敗した場合は try-catch でエラーをキャッチしてエラーページにリダイレクト
- 取得したユーザ情報を
authContextに保存
import { Outlet, redirect, createContext } from "react-router";
import { rootContext } from "~/root";
import type { Route } from "./+types/_auth";
import { getAxios } from "~/utilities/axios";
import type { User } from "~/types/api";
// middleware用のコンテキスト作成
export const authContext = createContext<User>();
// 認証済みチェックミドルウェア
const authMiddleware: Route.MiddlewareFunction = async ({ context }) => {
const authenticatedUser = context.get(rootContext);
if (!authenticatedUser?.userName) {
throw redirect("/error");
}
try {
// user情報取得処理
const response = await getAxios().get<User>(`auth/user/${authenticatedUser.userName}`);
const user = response.data;
if (!user || user.id === 0) {
throw redirect("/error");
}
context.set(authContext, user);
} catch (error) {
console.error("Failed to fetch user info:", error);
throw redirect("/error");
}
};
export const middleware: Route.MiddlewareFunction[] = [authMiddleware];
export default function AuthLayout() {
return <Outlet />;
}
使用方法のまとめ
-
環境変数の取得:
getEnv(key)を使用して環境変数を取得(初回呼び出し時に自動的に.envを読み込み) -
Axios の使用:
getAxios()を使用して Axios インスタンスを取得し、API を呼び出す(初回呼び出し時に自動的に初期化)
この方法により、サーバサイドで安全に環境変数を管理し、API 呼び出しを一元化できます。
注意事項
-
.envファイルは Git にコミットしない(.gitignoreに追加) -
.env.exampleファイルを作成し、必要な環境変数のサンプルを記載する -
env.tsのEnvKey型に新しい環境変数を追加する際は、型定義も更新する - この実装はサーバサイド専用のため、クライアントサイドでは使用できない