Remix & Cloudflare Pagesのプロジェクトで環境変数を使う
はじめに
Remix & Cloudflare Pages の環境に Firebase Authentication を導入するため環境変数の設定を行ったのですが、結構時間とられたので記事にしようと思います。
前提
以下の設定が完了している前提で進めていきます。
- Cloudflare Pages でデプロイする前提の Remix プロジェクトを作成している
-
npx create-remix@latest
を入力 -
What type of app do you want to create?
でJust the basics
を選択 -
Where do you want to deploy? Choose Remix App Server if you're unsure; it's easy to change deployment targets.
でCloudflare Pages
を選択
-
- Cloudflare Pages でデプロイできる
- Firebase のプロジェクトが作成されている
- Firebase Authentication でメールアドレスとパスワードによる認証が設定されている
環境変数の呼び出し方
package.json と同じ階層に.dev.vars
というファイルを作成します。ファイルの中身は.env ファイルと同じように書きます。環境変数は git 管理しないので.gitignore
に追記します。
SECRET_KEY = secret-key;
loader 関数内で環境変数の呼び出しができます。
export const loader: LoaderFunction = async ({ context }) => {
return {
SECRET_KEY: context.SECRET_KEY,
};
};
.dev.vars に環境変数を設定する
.dev.vars
に firebase app の項目を設定します。
API_KEY = YOUR_API_KEY
AUTH_DOMAIN = YOUR_AUTH_DOMAIN
PROJECT_ID = YOUR_PROJECT_ID
STORAGE_BUCKET = YOUR_STORAGE_BUCKET
MESSAGING_SENDER_ID = YOUR_MESSAGING_SENDER_ID
APP_ID = YOUR_APP_ID
MEASUREMENT_ID = YOUR_MEASUREMENT_ID
Firebase App を初期化する
ここではroot.tsx
内で firebase app の初期化を行います。loader 関数で環境変数を呼び出します。useLoaderData()
を使って loader 関数で return した json を取得します。呼び出した環境変数を使って firebase app を initialize します。
root.tsx
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useLoaderData,
} from "@remix-run/react";
import type { LoaderFunction, MetaFunction } from "@remix-run/cloudflare";
import type { FirebaseOptions } from "firebase/app";
import { initializeApp } from "firebase/app";
export const loader: LoaderFunction = async ({ context }) => {
return {
API_KEY: context.API_KEY,
AUTH_DOMAIN: context.AUTH_DOMAIN,
PROJECT_ID: context.PROJECT_ID,
STORAGE_BUCKET: context.STORAGE_BUCKET,
MESSAGING_SENDER_ID: context.MESSAGING_SENDER_ID,
APP_ID: context.APP_ID,
MEASUREMENT_ID: context.MEASUREMENT_ID,
};
};
export const meta: MetaFunction = () => ({
charset: "utf-8",
title: "New Remix App",
viewport: "width=device-width,initial-scale=1",
});
export default function App() {
const {
API_KEY,
AUTH_DOMAIN,
PROJECT_ID,
STORAGE_BUCKET,
MESSAGING_SENDER_ID,
APP_ID,
MEASUREMENT_ID,
} = useLoaderData();
const firebaseConfig: FirebaseOptions = {
apiKey: API_KEY,
authDomain: AUTH_DOMAIN,
projectId: PROJECT_ID,
storageBucket: STORAGE_BUCKET,
messagingSenderId: MESSAGING_SENDER_ID,
appId: APP_ID,
measurementId: MEASUREMENT_ID,
};
initializeApp(firebaseConfig);
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}
Firebase Authentication で新規登録する
新規登録画面としてapp\routes\login\entry.tsx
を作成します。
ユーザー名とパスワードを入力して新規登録ボタンを押すと、action
に POST されます。getAuth()
の部分で、root.tsx で initialize した FirebaseApp に関連付けられた Auth インスタンスを返します。action
内でユーザーの新規登録を行います。
entry.tsx
import { Form } from "@remix-run/react";
import type { ActionFunction } from "@remix-run/cloudflare";
import { redirect } from "@remix-run/cloudflare";
import { getAuth, createUserWithEmailAndPassword } from "firebase/auth";
export const action: ActionFunction = async ({ request }) => {
const formData = await request.formData();
const email = formData.get("email")?.toString();
const password = formData.get("password")?.toString();
if (email !== undefined && password !== undefined) {
try {
const firebaseAuth = getAuth();
const userCredential = await createUserWithEmailAndPassword(
firebaseAuth,
email,
password
);
console.log("createUser=", userCredential.user);
return redirect("/login");
} catch (error) {
console.log(error);
return redirect("/");
}
}
return redirect("/");
};
export default function Entry() {
return (
<>
<div>新規登録</div>
<Form method="post">
<fieldset>
<div>
<label htmlFor="email">ユーザー名</label>
<input type="email" name="email" id="email" />
</div>
<div>
<label htmlFor="password">パスワード</label>
<input type="password" name="password" id="password" />
</div>
<div>
<button type="submit">新規登録</button>
</div>
</fieldset>
</Form>
</>
);
}
Firebase Authentication でログインする
ログイン画面としてapp\routes\login\index.tsx
を作成します。
ユーザー名とパスワードを入力してログインボタンを押すと、action
に POST されます。getAuth()
の部分で、root.tsx で initialize した FirebaseApp に関連付けられた Auth インスタンスを返します。action
内でユーザーのログインを行います。
index.tsx
import { Form } from "@remix-run/react";
import type { ActionFunction } from "@remix-run/cloudflare";
import { redirect } from "@remix-run/cloudflare";
export const action: ActionFunction = async ({ request }) => {
const formData = await request.formData();
const email = formData.get("email")?.toString();
const password = formData.get("password")?.toString();
if (email !== undefined && password !== undefined) {
try {
const firebaseAuth = getAuth();
const userCredential = await signInWithEmailAndPassword(
firebaseAuth,
email,
password
);
console.log("loginUser=", userCredential.user);
return redirect("/top");
} catch (error) {
console.log(error);
return redirect("/");
}
}
return redirect("/");
};
export default function Login() {
return (
<>
<div>ログイン画面</div>
<Form method="post">
<fieldset>
<div>
<label htmlFor="email">ユーザー名</label>
<input type="email" name="email" id="email" />
</div>
<div>
<label htmlFor="password">パスワード</label>
<input type="password" name="password" id="password" />
</div>
<div>
<button type="submit">ログイン</button>
</div>
</fieldset>
</Form>
</>
);
}
ログインに成功した場合のみ遷移する画面としてapp\routes\top\index.tsx
を作成しておきます。
index.tsx
export default function Top() {
return <div>トップページ</div>;
}
実行すると、、、
npm run dev
でローカルサーバーを起動します。
登録していないユーザーでログインしようとするとauth/user-not-found
というエラーが返ってきます。今の実装では/
にリダイレクトされます。
code: 'auth/user-not-found',
新規登録画面からユーザーを登録するとaccessToken
などのユーザー情報が返ってきます。今の実装では/login
に遷移します。また、Firebase Authentication には入力したメールアドレスが登録されます。
すでに登録されているユーザーで新規登録しようとするとauth/email-already-in-use
というエラーが返ってきます。今の実装では/
にリダイレクトされます。
code: 'auth/email-already-in-use',
登録されているユーザーでログインすると、accessToken
等のユーザー情報が返ってきて/top
に遷移します。
最後に
Remix & Cloudflare Pages の環境で環境変数を呼ぶのは特殊な作業が必要だったので、解消するまでに時間がかかりました。.tsx
内に登録・ログイン処理を書きましたが、この部分は他ファイルに分けて、ロジックと表示の責任を分けるのがよいと思います。
本番環境で環境変数を使うには別途設定が必要なので、また記事にしようと思います。
Discussion