Metronome.fm のサインインは、これまで Google と GitHub のアカウントだけでした。サインインの画面は Clerk の UI を使わずに自前で作っていて、そこにメールアドレスの欄を足しました。メールアドレスを入れると確認コードが届き、そのコードを入れればサインインできます。パスワードは使いません。

- これまでの Google・GitHub のボタン
- 足したメールアドレスの欄
この記事では、ほかの手段を見送った理由、Clerk の設定、サインインとアカウントの作成を 1 つの欄で分ける書き方、E2E がボット対策で止まった原因を書きます。
Apple と X は見送った
Google と GitHub だけでは、iPhone の利用者や、GitHub を使わない演奏者には入りにくいと考えていました。そこで最初は Apple を足すつもりでした。
Web で Sign in with Apple を使うには、Apple の開発者向けの画面で Services ID と鍵を作る必要があり、それには Apple Developer Program への登録(年 99 ドル)が要ります。サインインのためだけに年会費を払うのは見合わないと判断しました。
X も調べました。X の API は 2026 年 2 月から従量課金だけになり、新しく始める開発者向けの無料の枠がありません。サインインのたびに Clerk が X のユーザーの情報を 1 件読むので、そのたびに 0.01 ドルかかり、クレジットは前払いです。残高が切れるとサインインできなくなる作りになるので、これも見送りました。
残ったのが、メールアドレスに届く確認コードです。追加の費用はかからず、Google などのアカウントを使いたくない人にも使ってもらえます。日本の利用者に届きやすい LINE は、別に足す予定です。
Clerk の設定
Clerk のダッシュボードの User & authentication › Email で、メールアドレスでのサインインと、アカウントを作るときの確認を有効にしました。開発と本番でインスタンスが別なので、どちらにも同じ設定を入れています。

- メールアドレスでのサインインを有効にする
- 確認の方法は、メールで届くコード
確認の方法には、コードのほかにメールの中のリンクを押す方法(Email verification link)もあります。リンクは、メールを開いた端末とアプリを開いている端末が違うと扱いがややこしくなるので、コードだけにしました。

- メールアドレスを必須にはしない
- アカウントを作るときに、届いたコードでメールアドレスを確かめる
メールアドレスを必須にする設定(Require email address)は、オフのままです。このあと足す LINE は、申請しないとメールアドレスを返しません。必須にすると、メールアドレスのない人が LINE でアカウントを作れなくなります。
設定が効いているかは、Clerk の Frontend API の /v1/environment でも確かめられます。サインインに使えるのが email_code になっていれば、ダッシュボードの設定が保存されています。
{
"used_for_first_factor": true,
"first_factors": ["email_code"],
"verifications": ["email_code"],
"verify_at_sign_up": true
}サインインかアカウントの作成かを、メールアドレスで分ける
Google や GitHub のボタンは、初めての人にはそのままアカウントを作ります。メールアドレスも同じにしたかったので、欄は 1 つにして、送った後で分けています。
まずサインインとして確認コードを送ります。そのメールアドレスのアカウントがなければ、Clerk は form_identifier_not_found のエラーを返すので、そのときはアカウントの作成として送り直します。
const sent = await signIn.emailCode.sendCode({ emailAddress });
if (!sent.error) {
setEmailCodeFlow("signIn");
setView({ kind: "emailCode", email: emailAddress });
return FORM_OK;
}
// このメールアドレスのアカウントがなければ、そのままアカウントを作る
const notFound = parseClerkError(sent.error).some(
(parsed) => parsed.kind === "identifierNotFound",
);
if (!notFound) return toErrorResult(sent.error, ["emailAddress"]);
const created = await signUp.create({ emailAddress });
if (created.error) return toErrorResult(created.error, ["emailAddress"]);
const signUpSent = await signUp.verifications.sendEmailCode();コードを確かめるときは、どちらで送ったかに合わせて signIn.emailCode.verifyCode か signUp.verifications.verifyEmailCode を呼び、終わっていれば finalize でサインインします。

- コードを送ったメールアドレス
- 届いたコードを入れる
Google で作ったアカウントと同じメールアドレスを入れた人は、新しいアカウントではなく、その Google のアカウントにサインインします。Google から受け取ったメールアドレスは、確認済みとしてアカウントに付いているからです。
E2E がボット対策で止まった
Clerk は、アカウントを作るときにボット対策(Cloudflare の Turnstile)を動かします。E2E では Clerk の @clerk/testing の setupClerkTestingToken を使い、ボット対策を飛ばしています。
ところが、初めてのメールアドレスでアカウントを作るテストだけが、メールアドレスを送ったところで止まりました。既存のアカウントにサインインするテストは通ります。
setupClerkTestingToken は、Clerk の Frontend API への通信を Playwright の route で受け、応答の中の captcha_bypass を true に書き換えています。ブラウザ側の Clerk はこの値が true なら Turnstile を飛ばします。書き換えるのは response と client の中だけで、エラーの応答の meta.client は書き換えません。
初めてのメールアドレスでは、最初のサインインが 422(アカウントがない)で失敗します。この応答の meta.client で captcha_bypass が false に戻り、続くアカウントの作成で Turnstile が動き、E2E のブラウザでは確認が終わらずに止まっていました。送る前と後で window.Clerk.client.captchaBypass を読むと、true から false に変わっていました。
E2E の中で、meta.client も同じように書き換える route を足して直しました。Playwright では、ページの route がブラウザコンテキストの route より先に動くので、setupClerkTestingToken の後に足せば、こちらが応答を返します。
await page.route(new RegExp(`^https://${escaped}/v1/`), async (route) => {
const url = new URL(route.request().url());
url.searchParams.set("__clerk_testing_token", testingToken);
const response = await route.fetch({ url: url.toString() });
const json: unknown = await response.json().catch(() => null);
if (!isRecord(json)) {
await route.fulfill({ response });
return;
}
for (const client of [
json.client,
isRecord(json.response) ? json.response : undefined,
isRecord(json.meta) ? json.meta.client : undefined,
]) {
if (isRecord(client) && client.captcha_bypass === false) {
client.captcha_bypass = true;
}
}
await route.fulfill({ response, json });
});確認コードは、Clerk の開発インスタンスのテスト用のメールアドレス(+clerk_test を含むアドレス)を使い、424242 で通しています。実際にはメールは送られません。
文言から手段の名前を外した
料金表の注記は「Googleサインインが必要」で、LP やドキュメントは「Google か GitHub」と書いていました。手段はこれからも増やすので、名前を並べない書き方に揃えました。料金表は「サインインが必要」、サインインの画面は「サインインの方法を選んでください」です。ドキュメントのように説明が要るところだけ、「Google などのアカウントか、メールアドレスで」と書いています。