Remix v2 でクライアントサイドに環境変数を渡す方法 


こんにちは。Gaji-Laboの村上です。
使われることが増えてきた Remix v2 で、クライアントサイドに環境変数を渡す方法について紹介します。
通常、環境変数はサーバーサイドでのみ使用されるべきですが、特定のケースではクライアントサイドで使用したい場合もあります。

ただし、クライアントサイドに渡す情報は第三者に見られる可能性があるため、非常に慎重に取り扱う必要があります。
APIキーやデータベースの接続情報などの機密情報は、絶対にクライアントサイドに渡さないでください。

1. window オブジェクト と dangerouslySetInnerHTML を使う

まず最初に、公式ドキュメントでも推奨されている window オブジェクト と dangerouslySetInnerHTML を使う方法を紹介します。
これは、ルートの loader 関数から環境変数を返し、インラインスクリプトを使用してクライアントサイドでグローバルに利用可能にする方法です。

1-1. .env ファイルの例

以下のような環境変数が .env ファイルに定義されていると仮定します。

HOGE_KEY="xxxxxxxhoge"
FUGA_KEY="xxxxxxxfuga"

1-2. ルートの loader からクライアントサイドに 環境変数を ENV として渡す

次に、loader 関数を使用して、これらの環境変数を ENV として渡します。

export async function loader() {
  return json({
    ENV: {
      HOGE_KEY: process.env.HOGE_KEY,
      FUGA_KEY: process.env.FUGA_KEY,
    },
  });
}

1-3. ENV を window オブジェクトに設定

クライアントサイドで useLoaderData と dangerouslySetInnerHTML を使用して、ENVwindow オブジェクト に設定します。

export async function loader() {
  return json({
    ENV: {
      HOGE_KEY: process.env.HOGE_KEY,
      FUGA_KEY: process.env.FUGA_KEY,
    },
  });
}

export function Root() {
  const data = useLoaderData<typeof loader>();
  return (
    <html lang="en">
      <head>
        <Meta />
        <Links />
      </head>
      <body>
        <Outlet />
        <script
          dangerouslySetInnerHTML={{
            __html: `window.ENV = ${JSON.stringify(
              data.ENV
            )}`,
          }}
        />
        <Scripts />
      </body>
    </html>
  );
}

1-4. window.ENV を確認する

ルート以外のページで window.ENV を確認してみましょう。
クライアントサイドでしかアクセスできないため、useEffect フックや if (typeof document !== "undefined")を使用して window.ENV の値が利用可能なタイミングでアクセスします。

export default function Foo() {
  if (typeof document !== "undefined") {
    console.log(window.ENV)
  }

  return (
    <div>
      <h1>foo</h1>
    </div>
  )
}

ページを表示すると、HOGE_KEY と FUGA_KEY がコンソールに表示されます。

1-5. window.ENV の型エラーを解消する

window.ENV を使用すると、TypeScript で次のような型エラーが発生します。
プロパティ 'ENV' は型 'Window & typeof globalThis' に存在しません。
これを解消するために、次のように型定義を行います。

interface Window {
  ENV: {
    HOGE_KEY: string;
    FUGA_KEY: string;
  };
}

これにより、TypeScriptでの型エラーが解消されます。

参考

https://remix.run/docs/en/main/guides/envvars
https://remix.run/docs/en/main/guides/constraints

2. useRouteLoaderDataを使う

2つ目の方法は、公式の GitHub の Discussions で提案されている、useRouteLoaderData フックを使う方法です。
window オブジェクトを使うのではなく、loader から返されるデータにアクセスするため、クライアントサイドの安全性を少し向上させたい場合に役立ちます。
特に、dangerouslySetInnerHTML を避けたい場合におすすめです。

2-1. ルート の loader からクライアントサイドに ENV を返す

まず、.envファイルに環境変数を設定し、loader 関数で環境変数を返します。
この部分は、「1. window.ENV と dangerouslySetInnerHTML を使う方法」と同じです。

HOGE_KEY="xxxxxxxhoge"
FUGA_KEY="xxxxxxxfuga"
export async function loader() {
  return json({
    ENV: {
      HOGE_KEY: process.env.HOGE_KEY,
      FUGA_KEY: process.env.FUGA_KEY,
    },
  });
}

2-2. useRouteLoaderData で ENV を利用する

ルート以外のページでも、useRouteLoaderData を使って ルート の loader から渡された環境変数にアクセスできます。
これにより、各ページで直接 ENV にアクセスできるため、環境変数を使いやすくなります。

import { useRouteLoaderData } from "@remix-run/react";
import type { loader as rootLoader } from "~/root";

export default function Foo() {
  const rootData = useRouteLoaderData<typeof rootLoader>("root")!;
  console.log(rootData.ENV);

  return (
    <div>
      <h1>foo</h1>
    </div>
  )
}

参考

https://github.com/remix-run/remix/discussions/8704#discussioncomment-8397380

3. Vite を使っている場合

Remix v2 は、デフォルトでビルドツールとして Vite を採用しています。
そのため、Vite を使っている場合は、環境変数の名前に VITE_ という接頭辞をつけることで、import.meta.env を使って環境変数にアクセスできます。

3-1. .env ファイルの設定

Vite ではクライアントサイドに渡したい環境変数に、接頭辞 VITE_ を設定します。

VITE_HOGE_KEY="xxxxxxxhoge"

3-2. クライアントサイドでの環境変数の使用

export default function Foo() {
  console.log(import.meta.env.VITE_HOGE_KEY);

  return (
    <div>
      <h1>foo</h1>
    </div>
  );
}

これにより、環境変数 VITE_HOGE_KEY の値がコンソールに表示されます。

参考

https://ja.vitejs.dev/guide/env-and-mode

4. root の ErrorBoundary で環境変数を使いたい場合

ErrorBoundary 内では、useLoaderData や useRouteLoaderData を使って、環境変数にアクセスすることはできません。
これは、useLoaderDataがErrorBoundary内で動作しないことや、useRouteLoaderData が undefined を返す場合があるためです。

それでも、root の ErrorBoundary 内で環境変数を使いたい場合には、公式の GitHub の Discussions で提案されているプロジェクトが参考になります。
https://github.com/kiliman/remix-global-data

具体的なユースケースとして、エラーハンドリングが必要な場合や、エラーページでも特定の環境変数を参照する必要がある場合に、このアプローチが役立ちます。

参考

https://github.com/remix-run/remix/discussions/9043
https://github.com/kiliman/remix-global-data

最後に

今回紹介した複数の方法を活用することで、状況に応じて最適な環境変数の管理・使用が可能になります。

Gaji-Labo はスタートアップに頭数や技術だけを提供するのではなく、チームの一員として成長を担うことを価値としています。
その価値を出せるよう(セキュリティを配慮するのは当然として)、この記事に書いたような状況に応じた判断や最適な実装ができる引き出しを増やしていきたいと思います。

そんなふうに専門技術を軸にさまざまなスタートアップへチームワークを提供したいエンジニアやデザイナーの方はとてもGaji-Labo に向いていると思います。
カジュアル面談でお話ししませんか? いますぐ転職を考えていない方でも歓迎です!

Gaji-Labo フロントエンドエンジニア向けご案内資料

今すぐの転職でなくてもOKです!まずはお話しませんか?

現在弊社では一緒にお仕事をしてくださるエンジニアさんやデザイナーさんを積極募集しています。まずはカジュアルな面談で、お互いに大事にしていることをお話できたらうれしいです。詳しい応募要項は以下からチェックしてください。

パートナー契約へのお問い合わせもお仕事へのお問い合わせも、どちらもいつでも大歓迎です。まずはオンラインでの面談でお話しましょう。ぜひお気軽にお問い合わせください!

話をしてみたい!

投稿者 Sena Murakami

アシスタントフロントエンドエンジニア。 Webマーケティング会社でマークアップエンジニアとして経験を積み、Gaji-Laboに入社。デザインの意図を汲み取ったマークアップが得意です。チームを俯瞰してリードできるエンジニアになることが目標です。趣味はバドミントンやバレーボール、キャンプなど。