> ## Documentation Index
> Fetch the complete documentation index at: https://translations.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Next.js アプリケーションに Login を追加する

> このガイドでは、Auth0 Next.js v4 SDK を使用して、新規または既存の Next.js アプリケーションに Auth0 を統合する方法を紹介します。

export const HowToSchema = () => <script type="application/ld+json">
    {'{"@context":"https://schema.org","@type":"HowTo"}'}
  </script>;

export const CreateInteractiveApp = ({placeholderText = "Auth0", appType = "regular_web", allowedCallbackUrls = ["localhost:3000"], allowedLogoutUrls = ["localhost:3000"], allowedOriginUrls = ["localhost:3000"]}) => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [storeReady, setStoreReady] = useState(false);
  const [displayForm, setDisplayForm] = useState(true);
  useEffect(() => {
    const init = () => setStoreReady(true);
    if (window.rootStore) {
      window.rootStore.clientStore.setSelectedClient(null);
      window.rootStore.clientStore.setSelectedClientSecret(undefined);
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
    };
  }, []);
  useEffect(() => {
    if (!storeReady) return;
    const disposer = autorun(() => {
      const rootStore = window.rootStore;
      setIsAuthenticated(rootStore.sessionStore.isAuthenticated);
    });
    return () => {
      disposer();
    };
  }, [storeReady]);
  if (!storeReady || typeof window === "undefined" || !displayForm) {
    return <></>;
  }
  const login = () => {
    const baseUrl = window.rootStore.config.apiBaseUrl;
    const returnTo = encodeURIComponent(window.location.href);
    window.location.href = `${baseUrl}/auth/user/login?returnTo=${returnTo}`;
  };
  const Card = ({className = "", children}) => {
    return <div className={`
          flex border rounded-2xl
          border-gray-950/10 dark:border-white/10
          py-3.5 px-4 gap-2
          text-sm text-gray-900 dark:text-gray-200
          ${className}
        `}>
        {children}
      </div>;
  };
  const Button = ({children, ...props}) => {
    return <button className="bg-[--button-primary] text-[--foreground-inverse] px-[1.125rem] py-1.5 rounded-lg font-medium" {...props}>
        {children}
      </button>;
  };
  const CreateApplicationForm = () => {
    const [name, setName] = useState("");
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState("");
    const handleSubmit = async () => {
      if (!name.trim()) {
        setError("アプリケーション名は必須です");
        return;
      }
      setIsLoading(true);
      setError(null);
      try {
        await window.rootStore.clientStore.createClient({
          name: name.trim(),
          app_type: appType,
          callbacks: allowedCallbackUrls,
          allowed_logout_urls: allowedLogoutUrls,
          web_origins: allowedOriginUrls,
          client_metadata: {
            created_by: "quickstart-docs-app-creation-component"
          }
        });
        setDisplayForm(false);
      } catch (err) {
        console.error("クライアントの作成中にエラーが発生しました:", err);
        const errorMessage = err instanceof Error ? err.message : "アプリケーションの作成に失敗しました";
        setError(errorMessage);
      } finally {
        setIsLoading(false);
      }
    };
    return <Card className="flex-col items-start p-4 gap-3.75">
        <span className="font-medium text-gray-900 dark:text-gray-200">Auth0 アプリの作成</span>
        <div className="w-full flex gap-2">
          <input id="app-name" name={name} className="
              w-full max-w-[448px] h-11 py-2 px-4 
              border rounded-lg border-gray-950/10 dark:border-white/10 
              text-gray-900 dark:text-gray-200
              focus:outline-none dark:focus:outline-none
            " placeholder={`My ${placeholderText} App`} value={name} onChange={e => setName(e.target.value)} />
          <Button onClick={handleSubmit}>{isLoading ? "作成中..." : "作成"}</Button>
        </div>
        {error && <p className="text-red-500">{error}</p>}
      </Card>;
  };
  const SignInForm = () => {
    return <Card className="items-center">
        <Button onClick={login}>ログイン</Button> <span>してアプリを作成してください</span>
      </Card>;
  };
  return isAuthenticated ? <CreateApplicationForm /> : <SignInForm />;
};

export const AuthCodeBlock = ({filename, icon, language, highlight, children}) => {
  const [displayText, setDisplayText] = useState(children);
  const [copyText, setCopyText] = useState(children);
  const wrapperRef = React.useRef(null);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      if (!window.autorun || !window.rootStore) {
        return;
      }
      unsubscribe = window.autorun(() => {
        let processedChildrenForDisplay = children;
        let processedChildrenForCopy = children;
        for (const [key, value] of window.rootStore.variableStore.values.entries()) {
          const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
          let displayValue = value;
          if (key === "{yourClientSecret}" && value !== "{yourClientSecret}") {
            displayValue = value.substring(0, 3) + "*****マスク済み*****";
          }
          processedChildrenForDisplay = processedChildrenForDisplay.replaceAll(new RegExp(escapedKey, "g"), displayValue);
          processedChildrenForCopy = processedChildrenForCopy.replaceAll(new RegExp(escapedKey, "g"), value);
        }
        setDisplayText(processedChildrenForDisplay);
        setCopyText(processedChildrenForCopy);
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  useEffect(() => {
    if (!wrapperRef.current) return;
    const originalWriteText = navigator.clipboard.writeText.bind(navigator.clipboard);
    let isOverriding = false;
    const handleClick = e => {
      const button = e.target.closest('[data-testid="copy-code-button"]');
      if (!button || !wrapperRef.current.contains(button)) return;
      isOverriding = true;
      navigator.clipboard.writeText = text => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
          return originalWriteText(copyText);
        }
        return originalWriteText(text);
      };
      setTimeout(() => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
        }
      }, 100);
    };
    const wrapper = wrapperRef.current;
    wrapper.addEventListener('click', handleClick, true);
    return () => {
      wrapper.removeEventListener('click', handleClick, true);
      if (navigator.clipboard.writeText !== originalWriteText) {
        navigator.clipboard.writeText = originalWriteText;
      }
    };
  }, [copyText]);
  return <div ref={wrapperRef}>
      <CodeBlock filename={filename} icon={icon} language={language} lines highlight={highlight}>
        {displayText}
      </CodeBlock>
    </div>;
};

export const AuthCodeGroup = ({children, dropdown}) => {
  const [processedChildren, setProcessedChildren] = useState(children);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      unsubscribe = window.autorun(() => {
        const processChildren = node => {
          if (typeof node === "string") {
            let processedNode = node;
            for (const [key, value] of window.rootStore.variableStore.values.entries()) {
              const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
              processedNode = processedNode.replaceAll(new RegExp(escapedKey, "g"), value);
            }
            return processedNode;
          } else if (Array.isArray(node)) {
            return node.map(processChildren);
          } else if (node && node.props && node.props.children) {
            return {
              ...node,
              props: {
                ...node.props,
                children: processChildren(node.props.children)
              }
            };
          }
          return node;
        };
        setProcessedChildren(processChildren(children));
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  return <CodeGroup dropdown={dropdown}>{processedChildren}</CodeGroup>;
};

<HowToSchema />

<Accordion title="AI を使って Auth0 を統合する" icon="microchip-ai" iconType="solid" defaultOpen>
  Claude Code、Cursor、GitHub Copilot などの AI コーディングアシスタントを使用している場合は、[agent skills](https://agentskills.io/home) を使って数分で Auth0 認証を自動的に追加できます。

  **インストール:**

  ```bash theme={null}
  npx skills add auth0/agent-skills --skill auth0-quickstart --skill auth0-nextjs
  ```

  **続けて、AI アシスタントに次のように依頼します:**

  ```text theme={null}
  Add Auth0 authentication to my Next.js app
  ```

  AI アシスタントは、Auth0 アプリケーションの作成、認証情報の取得、`@auth0/nextjs-auth0` のインストール、API ルートの作成、環境変数の設定を自動的に行います。[agent skills の詳細なドキュメント →](/ja/docs/quickstart/agent-skills)
</Accordion>

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  **前提条件:** 開始する前に、以下がインストールされていることを確認してください。

  * **[Node.js](https://nodejs.org/en/download)** 20 LTS 以降
  * **[npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)** 10+ または **[yarn](https://classic.yarnpkg.com/lang/en/docs/install/)** 1.22+ または **[pnpm](https://pnpm.io/installation)** 8+

  インストールを確認するには: `node --version && npm --version`
</Callout>

<div id="get-started">
  ## はじめに
</div>

このクイックスタートでは、Next.js 16 アプリケーションに Auth0 認証を追加する方法を説明します。Auth0 Next.js SDK を使用して、サーバーサイドレンダリング、セキュアなログイン機能、保護されたルートを備えたフルスタック Web アプリケーションを構築します。

export function generateRandomString(length) {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  return Array.from({
    length
  }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
}

export const localEnvSnippet = `AUTH0_DOMAIN={yourDomain}
AUTH0_CLIENT_ID={yourClientId}
AUTH0_CLIENT_SECRET={yourClientSecret}
AUTH0_SECRET=${generateRandomString(32)}
APP_BASE_URL=http://localhost:3000`;

<Steps>
  <Step title="新しいプロジェクトを作成" stepNumber={1}>
    このQuickstart用の新しいNext.jsプロジェクトを作成する

    ```shellscript theme={null}
    npx create-next-app@latest auth0-nextjs --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" --yes
    ```

    プロジェクトを開く

    ```shellscript theme={null}
    cd auth0-nextjs
    ```
  </Step>

  <Step title="Auth0 Next.js SDKをインストールする" stepNumber={2}>
    `shellscript npm install @auth0/nextjs-auth0 `
  </Step>

  <Step title="プロジェクトファイルを作成" stepNumber={3}>
    Auth0 統合に必要なディレクトリとファイルをすべて作成します。

    <CodeGroup>
      ```shellscript Mac/Linux theme={null}
      mkdir -p src/lib src/components && touch src/lib/auth0.ts src/proxy.ts src/components/LoginButton.tsx src/components/LogoutButton.tsx src/components/Profile.tsx
      ```

      ```powershell Windows theme={null}
      New-Item -ItemType Directory -Force -Path src/lib, src/components
      New-Item -ItemType File -Path src/lib/auth0.ts, src/proxy.ts, src/components/LoginButton.tsx, src/components/LogoutButton.tsx, src/components/Profile.tsx
      ```
    </CodeGroup>
  </Step>

  <Step title="Auth0 アプリを設定する" stepNumber={4}>
    次に、Auth0テナントで新しいアプリを作成し、プロジェクトに環境変数を追加します。

    Auth0アプリをセットアップするには、次の3つの方法があります。Quick Setupツールを使用する (推奨) 、CLIコマンドを実行する、またはDashboardから手動で設定する方法です。

    <Tabs>
      <Tab title="クイックセットアップ（推奨）">
        Auth0アプリを作成し、適切な設定値があらかじめ入力された `.env` ファイルをコピーします。

        <CreateInteractiveApp placeholderText="Next.js" appType="regular_web" allowedCallbackUrls={["http://localhost:3000/auth/callback"]} allowedLogoutUrls={["http://localhost:3000"]} allowedOriginUrls={["http://localhost:3000"]} />

        <AuthCodeBlock children={localEnvSnippet} language="shellscript" filename=".env.local" />
      </Tab>

      <Tab title="CLI">
        Auth0アプリを作成して `.env` ファイルを生成するには、プロジェクトのルートディレクトリで次のコマンドを実行します。

        <CodeGroup>
          ```shellscript Mac theme={null}
          # Auth0 CLI をインストール（未インストールの場合）
          brew tap auth0/auth0-cli && brew install auth0

          # Auth0 アプリをセットアップして .env ファイルを生成
          auth0 qs setup --app --type regular --framework nextjs --name "My App" --port 3000
          ```

          ```powershell Windows theme={null}
          # Auth0 CLI をインストール（未インストールの場合）
          scoop bucket add auth0 https://github.com/auth0/scoop-auth0-cli.git
          scoop install auth0

          # Auth0 アプリをセットアップして .env ファイルを生成
          auth0 qs setup --app --type regular --framework nextjs --name "My App" --port 3000
          ```
        </CodeGroup>

        <Callout icon="file-lines" color="#0EA5E9" iconType="regular">
          このコマンドでは次の処理が実行されます。

          1. 認証済みかどうかを確認し、必要に応じてログインを求めます
          2. `http://localhost:3000` 用に設定された Auth0 Regular Web Application を作成します
          3. `AUTH0_DOMAIN`、`AUTH0_CLIENT_ID`、`AUTH0_CLIENT_SECRET`、`AUTH0_SECRET`、`APP_BASE_URL` を含む `.env` ファイルを生成します
        </Callout>
      </Tab>

      <Tab title="Dashboard">
        プロジェクトのルートディレクトリに `.env.local` ファイルを作成します。

        ```shellscript .env.local theme={null}
        AUTH0_DOMAIN=YOUR_AUTH0_APP_DOMAIN
        AUTH0_CLIENT_ID=YOUR_AUTH0_APP_CLIENT_ID
        AUTH0_CLIENT_SECRET=YOUR_AUTH0_APP_CLIENT_SECRET
        AUTH0_SECRET=YOUR_LONG_RANDOM_SECRET_HERE
        APP_BASE_URL=http://localhost:3000
        ```

        **安全な AUTH0\_SECRET を生成する:**

        ```shellscript theme={null}
        openssl rand -hex 32
        ```

        出力をコピーし、`.env.local` の `YOUR_LONG_RANDOM_SECRET_HERE` を置き換えます。これは64文字の16進数である必要があります。

        次に、Auth0アプリケーションを設定します。

        1. [Auth0 Dashboard](https://manage.auth0.com/dashboard/) に移動します
        2. **Applications** > **Applications** > **Create Application** をクリックします
        3. ポップアップでアプリの名前を入力し、アプリケーションタイプとして `Regular Web Application` を選択して **Create** をクリックします
        4. Application Details ページで **Settings** タブに切り替えます
        5. `.env.local` ファイル内の `YOUR_AUTH0_APP_DOMAIN`、`YOUR_AUTH0_APP_CLIENT_ID`、`YOUR_AUTH0_APP_CLIENT_SECRET` を、Dashboard の **Domain**、**Client ID**、**Client Secret** の値に置き換えます

        <Warning>
          **重要:** アプリの実行開始後に `AUTH0_SECRET` を変更した場合は、localhost 用のブラウザ cookie を削除する必要があります。以前のシークレットで暗号化された古いセッションクッキーが原因で、`JWEDecryptionFailed` エラーが発生します。
        </Warning>

        最後に、Application Details ページの **Settings** タブで、次の URL を設定します。

        **Allowed Callback URLs:**

        ```
        http://localhost:3000/auth/callback
        ```

        **Allowed Logout URLs:**

        ```
        http://localhost:3000
        ```

        **Allowed Web Origins:**

        ```
        http://localhost:3000
        ```

        <Info>
          **Allowed Callback URLs** は、認証後にユーザーを安全にアプリケーションへ戻すための重要なセキュリティ対策です。一致する URL がない場合、ログイン処理は失敗し、ユーザーはアプリにアクセスできず、Auth0 のエラーページが表示されます。

          **Allowed Logout URLs** は、サインアウト時にシームレスなユーザー体験を提供するうえで重要です。一致する URL がない場合、ログアウト後にユーザーはアプリケーションへリダイレクトされず、汎用的な Auth0 ページに留まることになります。

          **Allowed Web Origins** はサイレント認証に不可欠です。これが設定されていないと、ユーザーはページを更新したときや、後でアプリに戻ったときにログアウトされます。
        </Info>
      </Tab>
    </Tabs>
  </Step>

  <Step title="Auth0 の設定を作成する" stepNumber={5}>
    `src/lib/auth0.ts` に Auth0 クライアントのコードを追加します:

    ```typescript src/lib/auth0.ts lines theme={null}
    import { Auth0Client } from '@auth0/nextjs-auth0/server';

    export const auth0 = new Auth0Client();
    ```
  </Step>

  <Step title="プロキシを追加" stepNumber={6}>
    プロキシコードを `src/proxy.ts` に追加します:

    ```typescript src/proxy.ts expandable lines theme={null}
    import { auth0 } from "./lib/auth0";

    export async function proxy(request: Request) {
      return await auth0.middleware(request);
    }

    export const config = {
      matcher: [
        /*
         * 以下で始まるパスを除く、すべてのリクエストパスに一致:
         * - _next/static (静的ファイル)
         * - _next/image (画像最適化ファイル)
         * - favicon.ico, sitemap.xml, robots.txt (メタデータファイル)
         */
        "/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)",
      ],
    };
    ```

    <Callout icon="file-lines" color="#0EA5E9" iconType="regular">
      `src/` ディレクトリを使用しているため、`proxy.ts` ファイルは `src/` 内に作成されます。`src/` ディレクトリを使用していない場合は、代わりにプロジェクトルートに作成してください。
    </Callout>

    <Info>
      このプロキシは、次の認証ルートを自動的にマウントします。

      * `/auth/login` - ログイン ルート
      * `/auth/logout` - ログアウト ルート
      * `/auth/callback` - コールバック ルート
      * `/auth/profile` - ユーザープロフィール ルート
      * `/auth/access-token` - アクセストークン ルート
      * `/auth/backchannel-logout` - バックチャネルログアウト ルート
    </Info>
  </Step>

  <Step title="Login、ログアウト、ユーザープロファイル用のコンポーネントを作成" stepNumber={7}>
    ステップ 3 で作成したファイルに、次のコンポーネントコードを追加します。

    <AuthCodeGroup>
      ```typescript src/components/LoginButton.tsx lines theme={null}
      "use client";

      export default function LoginButton() {
        return (
          <a
            href="/auth/login"
            className="w-full text-center inline-block px-6 py-3 bg-gradient-to-b from-[#2d2d42] to-[#161620] hover:opacity-90 text-white font-medium rounded-full text-[14px] transition-opacity"
          >
            Login
          </a>
        );
      }
      ```

      ```typescript src/components/LogoutButton.tsx lines theme={null}
      "use client";

      export default function LogoutButton() {
        return (
          <a
            href="/auth/logout"
            className="w-full text-center inline-block px-6 py-3 bg-[#f0f0f0] hover:bg-gray-200 text-gray-600 font-medium rounded-full text-[14px] transition-colors"
          >
            Logout
          </a>
        );
      }
      ```

      ```typescript src/components/Profile.tsx lines theme={null}
      "use client";

      import { useUser } from "@auth0/nextjs-auth0/client";

      function getInitials(name?: string | null, email?: string | null): string {
        if (name) {
          const parts = name.trim().split(" ");
          return parts.length >= 2
            ? `${parts[0][0]}${parts[1][0]}`.toUpperCase()
            : parts[0].slice(0, 2).toUpperCase();
        }
        if (email) return email.slice(0, 2).toUpperCase();
        return "U";
      }

      export default function Profile() {
        const { user, isLoading } = useUser();

        if (isLoading) return <p className="text-xs text-gray-500">Loading...</p>;
        if (!user) return null;

        return (
          <>
            <div className="flex items-center gap-2 text-green-500 text-[13px] font-medium">
              <span className="w-5 h-5 bg-green-500 rounded-full flex items-center justify-center shrink-0">
                <svg width="10" height="8" viewBox="0 0 10 8" fill="none">
                  <path d="M1 4L3.5 6.5L9 1" stroke="white" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
                </svg>
              </span>
              Successfully authenticated
            </div>
            <div className="flex items-center gap-2 bg-gray-100 rounded-full py-1.5 pl-1.5 pr-4 text-[12px] text-gray-700 max-w-full">
              <span className="w-7 h-7 bg-gradient-to-b from-[#2d2d42] to-[#161620] rounded-full flex items-center justify-center text-white text-[10px] font-semibold shrink-0">
                {getInitials(user.name, user.email)}
              </span>
              <span className="truncate">{user.email}</span>
            </div>
          </>
        );
      }
      ```
    </AuthCodeGroup>
  </Step>

  <Step title="メインページを更新" stepNumber={8}>
    `src/app/page.tsx` を以下の内容に置き換えます：

    ```typescript src/app/page.tsx lines theme={null}
    import { auth0 } from "@/lib/auth0";
    import LoginButton from "@/components/LoginButton";
    import LogoutButton from "@/components/LogoutButton";
    import Profile from "@/components/Profile";

    export default async function Home() {
      const session = await auth0.getSession();
      const user = session?.user;

      return (
        <main className="min-h-screen bg-[#efefef] flex flex-col items-center justify-center gap-4 px-6 py-12">
          <div className="bg-white rounded-[28px] shadow-[0_4px_32px_rgba(0,0,0,0.08)] px-12 py-14 flex flex-col items-center gap-4 w-[360px]">
            <svg width="68" height="68" viewBox="0 0 68 68" fill="none" xmlns="http://www.w3.org/2000/svg" className="mb-1">
              <g filter="url(#filter0_di)">
                <rect x="2" y="2" width="64" height="64" rx="16" fill="url(#paint0_linear)"/>
                <rect x="2.5" y="2.5" width="63" height="63" rx="15.5" stroke="#252525"/>
                <path d="M34.0002 18C25.1572 18 18 25.1669 18 34C18 42.8432 25.1672 50 34.0002 50C42.8333 50 50 42.8331 50 34C50 25.1669 42.8433 18 34.0002 18ZM43.9172 43.8971C43.9172 43.9071 43.9069 43.9072 43.9069 43.9172C43.9069 43.9172 43.8969 43.9272 43.8868 43.9272C43.144 44.65 41.9796 44.7303 41.0662 44.2585L40.0228 43.7265C36.2487 41.7792 31.7619 41.7792 27.9777 43.7265L26.9338 44.2585C26.0103 44.7303 24.8459 44.65 24.1132 43.9272C24.1132 43.9272 24.1031 43.9172 24.0931 43.9172C24.0931 43.9172 24.0828 43.9071 24.0828 43.8971C23.3601 43.1543 23.2797 41.9899 23.7515 41.0765L24.2837 40.0326C26.231 36.2585 26.231 31.7717 24.2837 27.9975L23.7515 26.9536C23.2797 26.0302 23.3601 24.8657 24.0828 24.133C24.0828 24.1229 24.0931 24.123 24.0931 24.123C24.0931 24.123 24.1031 24.1129 24.1132 24.1129C24.856 23.3902 26.0204 23.3099 26.9338 23.7817L27.9777 24.3137C31.7518 26.261 36.2386 26.261 40.0228 24.3137L41.0662 23.7817C41.9897 23.3099 43.1541 23.3902 43.8868 24.1129C43.8868 24.1129 43.8969 24.123 43.9069 24.123L43.9172 24.133C44.6399 24.8758 44.7203 26.0402 44.2485 26.9536L43.7163 27.9975C41.769 31.7717 41.769 36.2585 43.7163 40.0326L44.2485 41.0765C44.7203 41.9899 44.6499 43.1543 43.9172 43.8971Z" fill="white"/>
              </g>
              <defs>
                <filter id="filter0_di" x="0" y="0" width="68" height="68" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
                  <feFlood floodOpacity="0" result="BackgroundImageFix"/>
                  <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
                  <feMorphology radius="2" operator="dilate" in="SourceAlpha" result="effect1_dropShadow"/>
                  <feOffset/>
                  <feComposite in2="hardAlpha" operator="out"/>
                  <feColorMatrix type="matrix" values="0 0 0 0 0.117647 0 0 0 0 0.129412 0 0 0 0 0.164706 0 0 0 0.12 0"/>
                  <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
                  <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
                  <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
                  <feOffset dy="-1"/>
                  <feGaussianBlur stdDeviation="0.5"/>
                  <feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
                  <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/>
                  <feBlend mode="normal" in2="shape" result="effect2_innerShadow"/>
                </filter>
                <linearGradient id="paint0_linear" x1="34" y1="2" x2="34" y2="66" gradientUnits="userSpaceOnUse">
                  <stop/>
                  <stop offset="1" stopColor="#677190"/>
                </linearGradient>
              </defs>
            </svg>

            {user ? (
              <>
                <h1 className="text-[17px] font-bold text-gray-900 tracking-tight">アカウント</h1>
                <div className="w-full h-px bg-gray-100" />
                <Profile />
                <LogoutButton />
              </>
            ) : (
              <>
                <h1 className="text-[17px] font-bold text-gray-900 tracking-tight">Sample0 へようこそ</h1>
                <p className="text-[13px] text-gray-400 text-center leading-relaxed -mt-2">
                  アカウントにログインして始めましょう
                </p>
                <div className="h-3" />
                <LoginButton />
              </>
            )}
          </div>

          <div className="flex items-center gap-1.5 text-[11px] text-gray-400">
            <span>Powered by</span>
            <img
              src="https://cdn.auth0.com/quantum-assets/dist/latest/logos/auth0/auth0-lockup-en-onlight.svg"
              alt="Auth0"
              className="h-3 opacity-40"
            />
          </div>
        </main>
      );
    }
    ```
  </Step>

  <Step title="Auth0Provider を使用してレイアウトを更新する" stepNumber={9}>
    `src/app/layout.tsx` を更新して Inter フォントを読み込み、アプリを `Auth0Provider` で囲みます。

    ```typescript src/app/layout.tsx lines theme={null}
    import { Inter } from "next/font/google";
    import type { Metadata } from "next";
    import { Auth0Provider } from "@auth0/nextjs-auth0/client";
    import "./globals.css";

    const inter = Inter({
      subsets: ["latin"],
      weight: ["300", "400", "500", "600", "700"],
    });

    export const metadata: Metadata = {
      title: "Auth0 Next.js App",
      description: "Next.js app with Auth0 authentication",
    };

    export default function RootLayout({
      children,
    }: {
      children: React.ReactNode;
    }) {
      return (
        <html lang="en">
          <body className={inter.className}>
            <Auth0Provider>{children}</Auth0Provider>
          </body>
        </html>
      );
    }
    ```

    <Callout icon="file-lines" color="#0EA5E9" iconType="regular">
      v4 では、Auth0Provider は省略可能です。必要なのは、サーバー レンダリング時に初期ユーザーを渡し、それを `useUser()` フックで利用できるようにする場合だけです。
    </Callout>
  </Step>

  <Step title="Tailwind CSS を設定する" stepNumber={10}>
    `src/app/globals.css` の内容を次の内容に置き換えます。

    ```css src/app/globals.css lines theme={null}
    @import "tailwindcss";
    ```
  </Step>

  <Step title="アプリを実行する" stepNumber={11}>
    ```shellscript theme={null}
    npm run dev
    ```

    <Info>
      アプリは [http://localhost:3000](http://localhost:3000) で利用できます。Auth0 SDK v4 は、認証ルートを `/auth/*` に自動的にマウントします (v3 の `/api/auth/*` ではありません) 。

      ポート 3000 が使用中の場合は、`npm run dev -- --port 3001` を実行し、Auth0 アプリの callback URL を `http://localhost:3001` に更新してください。
    </Info>
  </Step>
</Steps>

<Check>
  **チェックポイント**

  これで、Auth0 のログインページが [localhost](http://localhost:3000/) 上で正常に動作しているはずです
</Check>

***

<div id="troubleshooting">
  ## トラブルシューティング
</div>

<Accordion title="JWEDecryptionFailed エラー">
  `JWEDecryptionFailed: decryption operation failed` エラーが表示される場合は、`AUTH0_SECRET` が無効であるか、別のシークレットで暗号化された古いセッションクッキーが原因です。

  **解決策:**

  1. 次のコマンドで新しいシークレットを生成します:

  ```shellscript theme={null}
  openssl rand -hex 32
  ```

  2. `.env.local` ファイルを更新します:

  ```
  AUTH0_SECRET=<your-new-64-character-hex-string>
  ```

  3. `localhost:3000` の**ブラウザ cookies を削除**します:
     * Chrome/Edge: `F12` キーを押す → \[Application] タブ → \[Cookies] → localhost の cookies をすべて削除
     * Firefox: `F12` キーを押す → \[Storage] タブ → \[Cookies] → localhost の cookies をすべて削除
     * Safari: \[Develop] メニュー → \[Show Web Inspector] → \[Storage] タブ → \[Cookies] → すべて削除

  4. 開発サーバーを再起動します:

  ```shellscript theme={null}
  npm run dev
  ```

  シークレットは必ず 32 バイト (16 進数 64 文字) である必要があります。このエラーは、アプリが別のシークレットで暗号化された既存のセッションクッキーを復号しようとしたときに発生します。
</Accordion>

<Accordion title="/auth/login での 404 エラー">
  ログイン をクリックすると 404 ページが表示される場合は、次の一般的な問題を確認してください:

  1. **プロキシの配置場所:** `src/proxy.ts` が正しい場所にあることを確認します
  2. **プロキシコード:** プロキシが手順 6 のコードと一致していることを確認します
  3. **サーバーの再起動:** プロキシファイルを作成した後、開発サーバーを再起動します
  4. **import の確認:** `import { auth0 } from "./lib/auth0"` のパスが正しいことを確認します
</Accordion>

<Accordion title="Module Not Found エラー">
  "Cannot find module '@/components/LoginButton'" のようなエラーが表示される場合:

  1. **ファイルの存在確認:** 手順 3 のファイルがすべて作成されていることを確認します
  2. **パスの確認:** コンポーネントが `src/components/` ディレクトリにあることを確認します
  3. **TypeScript の再起動:** `Cmd+Shift+P` (Mac) または `Ctrl+Shift+P` (Windows) を押して、"TypeScript: Restart TS Server" を実行します
  4. **import の確認:** `@/components/*` を使用していることを確認します (`~/components/*` ではありません)
</Accordion>

***

<div id="advanced-usage">
  ## 高度な使い方
</div>

<Accordion title="v4 の重要な変更点">
  このクイックスタートでは **Auth0 Next.js SDK v4** を使用します。v3 から大きな変更があります。

  * **動的ルートハンドラーは不要** - 認証ルートはプロキシによって自動的にマウントされます
  * **クライアント設定の簡素化** - `new Auth0Client()` は環境変数を自動的に読み込みます
  * **新しいルートパス** - ルートは `/api/auth/*` ではなく `/auth/*` に配置されます
  * **プロキシが必須** - すべての認証機能は `proxy.ts` 経由で動作します
  * **`<a>` タグを使用** - ナビゲーションには、onClick を使うボタンではなく `<a href="/auth/login">` を使用する必要があります

  ### 認証ルート

  SDK は、プロキシ経由で以下のルートを自動的にマウントします。

  | Route                      | Purpose         |
  | -------------------------- | --------------- |
  | `/auth/login`              | ログインを開始         |
  | `/auth/logout`             | ユーザーをログアウト      |
  | `/auth/callback`           | Auth0 コールバックを処理 |
  | `/auth/profile`            | ユーザープロフィールを取得   |
  | `/auth/access-token`       | アクセストークンを取得     |
  | `/auth/backchannel-logout` | バックチャネルログアウトを処理 |

  <Callout icon="file-lines" color="#0EA5E9" iconType="regular">
    これらのルートで 404 エラーが発生する場合は、次の点を確認してください。

    1. `proxy.ts` ファイルが正しい場所にあること (プロジェクトルート、または `src/` ディレクトリを使用している場合は `src/` 内)
    2. 手順 6 に記載の matcher パターンでプロキシが正しく設定されていること
    3. プロキシファイルの作成後に開発サーバーを再起動していること
  </Callout>
</Accordion>

<Accordion title="サーバーサイド認証">
  Auth0 Next.js SDK v4 は、App Router と Pages Router の両方のパターンをサポートしています。以下は、一般的なサーバーサイドパターンの例です。

  <Tabs>
    <Tab title="App Router - Server Component">
      ```typescript app/protected/page.tsx theme={null}
      import { auth0 } from "@/lib/auth0";
      import { redirect } from "next/navigation";

      export default async function ProtectedPage() {
        const session = await auth0.getSession();

        if (!session) {
          redirect('/auth/login');
        }

        return (
          <div>
            <h1>Protected Content</h1>
            <p>Welcome, {session.user.name}!</p>
          </div>
        );
      }
      ```
    </Tab>

    <Tab title="App Router - API Route">
      ```typescript app/api/protected/route.ts theme={null}
      import { auth0 } from "@/lib/auth0";
      import { NextResponse } from "next/server";

      export async function GET() {
        const session = await auth0.getSession();

        if (!session) {
          return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
        }

        return NextResponse.json({
          message: "This is a protected API route",
          user: session.user
        });
      }
      ```
    </Tab>

    <Tab title="Pages Router - Page">
      ```typescript pages/protected.tsx theme={null}
      import { auth0 } from "@/lib/auth0";
      import { GetServerSideProps } from "next";

      export default function ProtectedPage({ user }: { user: any }) {
        return (
          <div>
            <h1>Protected Content</h1>
            <p>Welcome, {user.name}!</p>
          </div>
        );
      }

      export const getServerSideProps: GetServerSideProps = auth0.withPageAuthRequired();
      ```
    </Tab>
  </Tabs>
</Accordion>

<Accordion title="クライアントサイド認証">
  クライアントサイドで認証状態を扱うには、`useUser` フックを使用します。

  ```typescript components/UserProfile.tsx theme={null}
  "use client";

  import { useUser } from "@auth0/nextjs-auth0/client";

  export default function UserProfile() {
    const { user, error, isLoading } = useUser();

    if (isLoading) return <div>Loading...</div>;
    if (error) return <div>Error: {error.message}</div>;
    if (!user) return <div>Not logged in</div>;

    return (
      <div>
        <h2>{user.name}</h2>
        <p>{user.email}</p>
        <img src={user.picture} alt="Profile" referrerPolicy="no-referrer" />
      </div>
    );
  }
  ```
</Accordion>

<Accordion title="API ルートの保護">
  API ルートを保護するには、`withApiAuthRequired` メソッドを使用します。

  ```typescript app/api/protected/route.ts theme={null}
  import { auth0 } from "@/lib/auth0";

  export const GET = auth0.withApiAuthRequired(async function handler() {
    const session = await auth0.getSession();

    return Response.json({
      message: "This is a protected API route",
      user: session?.user
    });
  });
  ```
</Accordion>

<Accordion title="サードパーティのバックエンドで Auth0 トークンを使用する">
  Auth0 の認証トークンを必要とするサードパーティのバックエンドサービス (Convex、Supabase、Firebase など) を使用している場合は、Next.js アプリからバックエンドクライアントにアクセストークンを渡す必要があります。

  ### アクセストークンの取得

  **サーバーサイド (App Router) :**

  ```typescript app/api/token/route.ts theme={null}
  import { auth0 } from "@/lib/auth0";
  import { NextResponse } from "next/server";

  export async function GET() {
    const session = await auth0.getSession();

    if (!session) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }

    const accessToken = session.accessToken;
    return NextResponse.json({ accessToken });
  }
  ```

  **クライアントサイド:**

  ```typescript lib/convex-client.ts theme={null}
  "use client";

  import { ConvexProviderWithAuth0 } from "convex/react-auth0";
  import { ConvexReactClient } from "convex/react";

  const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

  export function ConvexClientProvider({ children }: { children: React.ReactNode }) {
    return (
      <ConvexProviderWithAuth0
        client={convex}
        useAuth0={() => ({
          // Next.js の API ルートからアクセストークンを取得
          getAccessToken: async () => {
            const response = await fetch("/api/token");
            const { accessToken } = await response.json();
            return accessToken;
          },
        })}
      >
        {children}
      </ConvexProviderWithAuth0>
    );
  }
  ```

  ### バックエンドの設定

  ほとんどのサードパーティサービスでは、トークンを検証するために Auth0 のドメインとオーディエンスが必要です。バックエンドの設定では、次のように指定します。

  ```typescript convex/auth.config.ts theme={null}
  export default {
    providers: [
      {
        domain: process.env.AUTH0_DOMAIN,
        applicationID: process.env.AUTH0_CLIENT_ID,
      },
    ],
  };
  ```

  <Callout icon="file-lines" color="#0EA5E9" iconType="regular">
    バックエンドで必要になる場合は、Auth0 アプリケーションに API オーディエンスが設定されていることを確認してください。これは Auth0 Dashboard の Applications → APIs で設定するか、`.env.local` に `AUTH0_AUDIENCE` を追加し、それに応じて SDK を設定することで対応できます。
  </Callout>

  ### トークンに関する問題のトラブルシューティング

  バックエンドで `ctx.auth.getUserIdentity()` が `null` を返す場合:

  1. **トークンが渡されていることを確認する:** ブラウザーの DevTools の Network タブで、リクエストにトークンが含まれていることを確認します
  2. **トークン形式を確認する:** `idToken` ではなく `accessToken` を渡していることを確認します
  3. **バックエンド設定を確認する:** バックエンドに正しい Auth0 のドメインとクライアントIDが設定されていることを確認します
  4. **オーディエンスを確認する:** Auth0 API を使用している場合は、`AUTH0_AUDIENCE` が設定されており、API 識別子と一致していることを確認します
  5. **トークンのクレームを確認する:** [jwt.io](https://jwt.io) で JWT をデコードし、想定どおりのクレームが含まれていることを確認します
</Accordion>
