login.tsx
以下のコードは、Next.js アプリケーションの「ログイン」ページの実装の一部であり、いくつかの重要なポイント(クライアントサイドレンダリング、動的インポート、Head
コンポーネントなど)があります。それぞれを詳しく解説します。
'use client'
import dynamic from 'next/dynamic'
import Head from 'next/head'
const Content = dynamic(
() => import('./components/Content').then((module) => module.Content),
{
ssr: false,
}
)
export default function Login() {
return (
<>
<Head>
<title>Top</title>
</Head>
<Content />
</>
)
}
1. 'use client'
の意味
'use client'
このディレクティブは、Next.js 13 以降で導入された App Router 構造におけるものです。この宣言は、このファイルがクライアントサイドで実行されることを示します。通常、Next.js はサーバーサイドレンダリング(SSR)とクライアントサイドレンダリング(CSR)を自動的に分けて処理しますが、'use client'
を使うことで、明示的にクライアント側でのみ実行するコンポーネントや処理を指定することができます。
このファイルでは、クライアントサイドでのインタラクティブな処理が必要であることを示しており、クライアントサイドで動的にコンポーネントをレンダリングする設定がされています。
2. dynamic
の使用と ssr: false
の指定
import dynamic from 'next/dynamic'
const Content = dynamic(
() => import('./components/Content').then((module) => module.Content),
{
ssr: false,
}
)
dynamic
: Next.js のdynamic
関数は、コンポーネントを動的にインポート(遅延読み込み)するために使います。これにより、アプリケーションの初期読み込み時間を短縮し、パフォーマンスを最適化します。ssr: false
:ssr
(Server-Side Rendering)の設定をfalse
にすることで、このコンポーネントがサーバーサイドではレンダリングされず、クライアントサイドでのみ動的に読み込まれることを指定しています。Content
コンポーネントは、クライアントサイドでのみ必要な処理を行うため、サーバーではレンダリングされません。- 動的インポートの利点: このアプローチは、クライアントサイドでのみ実行される機能(例: ブラウザ API を利用するもの)を持つコンポーネントに適しています。サーバーサイドでは不要なクライアント依存のコードを回避し、必要になったときにのみクライアントサイドでレンダリングすることで、パフォーマンス向上を図ります。
3. Head
コンポーネント
import Head from 'next/head'
<Head>
<title>Top</title>
</Head>
Head
コンポーネント: このコンポーネントは、Next.js で HTML の<head>
要素に対するカスタマイズを行うために使用されます。ここでは、ページの<title>
要素を設定しています。これにより、ブラウザのタブに表示されるページのタイトルが「Top」に設定されます。
4. Login
コンポーネント
export default function Login() {
return (
<>
<Head>
<title>Top</title>
</Head>
<Content />
</>
)
}
Login
関数コンポーネント: このコンポーネントは、ログインページのエクスポートデフォルトコンポーネントであり、以下の要素を含みます:<Head>
: 上記の通り、<title>
を設定します。<Content />
: 動的に読み込まれるContent
コンポーネントをレンダリングします。このコンポーネントはssr: false
で指定されているため、クライアントサイドでのみ表示されます。
Content.tsx
以下のコードのログイン機能に関する部分は、Auth0 を使用した認証の実装がメインです。具体的には、Auth0 の loginWithRedirect
メソッドを使い、ユーザーを Auth0 のログインページにリダイレクトし、その後にログインのコールバックを処理する仕組みが含まれています。
'use client'
import { useAuth0 } from '@auth0/auth0-react'
import { App as CapApp } from '@capacitor/app'
import { Browser } from '@capacitor/browser'
import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { getDeviceInfo } from '@/lib/utils'
import AppleIcon from '@/assets/icons/apple.svg'
import GoogleIcon from '@/assets/icons/google.svg'
import LineIcon from '@/assets/icons/line.svg'
import LockIcon from '@/assets/icons/lock.svg'
import ProfileIcon from '@/assets/icons/profile.svg'
const Content = (): JSX.Element => {
const intl = useIntl()
const { loginWithRedirect, handleRedirectCallback } = useAuth0()
const router = useRouter()
const handleLogin = async () => {
try {
const deviceInfo = await getDeviceInfo()
console.log("here12", deviceInfo)
if (deviceInfo.platform === 'web') {
await loginWithRedirect()
return
}
await loginWithRedirect({
async openUrl(url) {
await Browser.open({
url,
windowName: '_self',
})
},
})
} catch (error) {
console.error(error)
}
}
useEffect(() => {
const handleMobileRedirectCallback = async () => {
const deviceInfo = await getDeviceInfo()
console.log("here1:", deviceInfo)
if (deviceInfo.platform === 'web') return
// Handle the 'appUrlOpen' event and call `handleRedirectCallback`
CapApp.addListener('appUrlOpen', async ({ url }) => {
if (
url.includes('state') &&
(url.includes('code') || url.includes('error'))
) {
await handleRedirectCallback(url).then(() => {
router.replace('/validate-login')
})
}
// No-op on Android
await Browser.close()
})
}
handleMobileRedirectCallback()
}, [handleRedirectCallback, router])
return (
<>
<div className="flex items-center gap-4">
<Image src="/images/logo-dark.svg" width={80} height={80} alt="Logo" />
<h1 className="text-4xl font-bold text-neutral-10">
<FormattedMessage id="general.company_name" />
</h1>
</div>
<article className="mt-8">
<h2 className="text-neutral-40">
<FormattedMessage id="page.login.form_title" />
</h2>
<form className="mt-4">
<div className="flex flex-col gap-6">
<Input
type="email"
placeholder="Your email"
icon={<ProfileIcon width={14} />}
/>
<Input
type="password"
placeholder={intl.formatMessage({
id: 'page.login.password_placeholder',
})}
icon={<LockIcon width={14} />}
/>
</div>
<div className="mt-6 flex flex-col items-end gap-3">
<Link href="/forgot-email" className="text-sm">
<FormattedMessage id="page.login.forgot_email" />
</Link>
<Link href="/forgot-password" className="text-sm">
<FormattedMessage id="page.login.forgot_password" />
</Link>
</div>
<div className="mt-10 flex justify-center">
<Button
size="lg"
type="button"
className="w-48"
data-testid="login-button"
onClick={handleLogin}
>
<FormattedMessage id="general.button_login" />
</Button>
</div>
<p className="mt-8 text-center text-sm text-neutral-30">
<FormattedMessage id="page.login.login_with" />
</p>
</form>
<div
role="group"
aria-label="other login options"
className="mt-2 flex justify-center gap-4"
>
<Button variant="outline" className="w-28">
<GoogleIcon width={20} />
<span className="sr-only">Google</span>
</Button>
<Button variant="outline" className="w-28">
<LineIcon width={20} />
<span className="sr-only">Line</span>
</Button>
<Button variant="outline" className="w-28">
<AppleIcon width={20} />
<span className="sr-only">Apple</span>
</Button>
</div>
<p className="mt-8 text-center">
<FormattedMessage
id="page.login.dont_have_account"
values={{
a: (...chunks) => <Link href="/create-account">{chunks}</Link>,
}}
/>
</p>
<div className="mt-20 text-center">
<Link href="/top" className="italic">
<FormattedMessage id="general.top" />
</Link>
</div>
</article>
</>
)
}
export { Content }
1. useAuth0
フックの使用
const { loginWithRedirect, handleRedirectCallback } = useAuth0()
useAuth0
: これは Auth0 の React SDK が提供するフックで、ログインやログアウト、認証状態の管理などを行う機能を提供します。loginWithRedirect
: このメソッドは、ユーザーを Auth0 のホスティングするログインページにリダイレクトし、ログイン処理を行います。ログインが成功した後、Auth0 が指定されたリダイレクト URL に戻ります。handleRedirectCallback
: ログイン後に、Auth0 のコールバック URL にリダイレクトされた際に、このメソッドを使用して認証トークンを取得し、ログインプロセスを完了します。
2. ログイン処理 (handleLogin
)
const handleLogin = async () => {
try {
const deviceInfo = await getDeviceInfo()
if (deviceInfo.platform === 'web') {
await loginWithRedirect()
return
}
await loginWithRedirect({
async openUrl(url) {
await Browser.open({
url,
windowName: '_self',
})
},
})
} catch (error) {
console.error(error)
}
}
handleLogin
関数: この関数は、ユーザーがログインボタンをクリックしたときに呼び出され、Auth0 の認証ページへのリダイレクトを処理します。
getDeviceInfo
の呼び出し:- この関数は、デバイス情報(
platform
)を取得します。これは、現在の環境がウェブブラウザかモバイルデバイスかを判別するために使用されます。
- この関数は、デバイス情報(
- プラットフォームの確認:
deviceInfo.platform === 'web'
の場合、loginWithRedirect
をそのまま呼び出します。この場合、通常のウェブブラウザ上でのリダイレクト処理が行われます。
- モバイルデバイスの場合:
- モバイルデバイスの場合は、
loginWithRedirect
にカスタムオプションを渡します。openUrl
というカスタム関数を定義しており、Browser.open
を使って、Capacitor のブラウザプラグインを利用し、Auth0 のログインページをモバイルブラウザで開きます。
- モバイルデバイスの場合は、
3. ログイン後のリダイレクト処理 (useEffect
)
useEffect(() => {
const handleMobileRedirectCallback = async () => {
const deviceInfo = await getDeviceInfo()
if (deviceInfo.platform === 'web') return
// Handle the 'appUrlOpen' event and call `handleRedirectCallback`
CapApp.addListener('appUrlOpen', async ({ url }) => {
if (
url.includes('state') &&
(url.includes('code') || url.includes('error'))
) {
await handleRedirectCallback(url).then(() => {
router.replace('/validate-login')
})
}
await Browser.close()
})
}
handleMobileRedirectCallback()
}, [handleRedirectCallback, router])
useEffect
フック: このフックは、コンポーネントの初回レンダリング時に実行され、モバイルデバイスでのリダイレクト処理を監視します。[handleRedirectCallback, router]
は、useEffect
フックの依存配列です。この依存配列は、useEffect
の中で使用している変数や関数に基づいて、どのタイミングでuseEffect
の処理が再実行されるかを決定します。handleRedirectCallback
は、Auth0 のuseAuth0
フックから提供される関数で、Auth0 による認証のリダイレクト処理が終わった後に、認証トークンの検証やユーザーの認証状態を更新するために使われます。この関数が依存配列に含まれていることで、handleRedirectCallback
の参照が変更された場合にuseEffect
が再度実行されます。handleRedirectCallback
の実装や参照が変わったときに、それを反映するためにuseEffect
が再度実行され、最新のhandleRedirectCallback
を使ってモバイルデバイス上でのリダイレクト処理が正しく行われるようにしています。router
は、Next.js のuseRouter
フックから提供されるルーティング関連のオブジェクトで、ユーザーを特定のページにリダイレクトしたり、ページ遷移を制御するために使います。このコードでは、ログイン処理が完了した後に、router.replace('/validate-login')
を呼び出して、/validate-login
ページに遷移させています。router
の参照が変わったとき、例えばユーザーが別のページに移動した場合や、router
に新しい挙動が追加された場合に、useEffect
が再度実行されます。これにより、最新のrouter
オブジェクトを使って、リダイレクト処理が適切に機能するようにしています。- 再レンダリング時の再実行の防止:
useEffect
は、依存配列に含まれる値が変更された場合にのみ再実行されます。つまり、handleRedirectCallback
やrouter
が変更されない限り、useEffect
内の処理はコンポーネントが再レンダリングされても再実行されません。 - 正しい値の参照: 依存配列に含めることで、
useEffect
内で常に最新のhandleRedirectCallback
とrouter
を使えるようにし、意図しない古い参照を使用することを防ぎます。
CapApp.addListener('appUrlOpen')
: このリスナーは、モバイルアプリが URL を開いたときに発火し、リダイレクト先の URL に含まれる認証情報(state
やcode
パラメータ)をチェックします。handleRedirectCallback(url)
: このメソッドが呼び出され、Auth0 からのリダイレクト URL を処理し、認証情報をもとにユーザーのログイン状態を確立します。router.replace('/validate-login')
: 認証後、/validate-login
ページにリダイレクトして、ログインが成功したことを通知します。
4. ログインボタンのクリックイベント
<Button
size="lg"
type="button"
className="w-48"
data-testid="login-button"
onClick={handleLogin}
>
<FormattedMessage id="general.button_login" />
</Button>
onClick={handleLogin}
: このボタンがクリックされると、上記のhandleLogin
関数が呼び出され、ログイン処理が開始されます。