# Custom auth pages This guide explains how to build your own auth pages (login, register, forgot password, reset password, confirm email, logout) so they match your site’s layout and style. For a basic site you can keep using the [default auth page](#default-auth-page). ## Overview You can use a **custom page for every auth flow**: | Page | Component | Server action(s) | |-----------------|-----------------------|-------------------------------------| | Login | `LoginPage` | `loginAction`, `setSessionCookie` | | Register | `RegisterPage` | `registerAction` | | Forgot password | `ForgotPasswordPage` | `forgotPasswordAction` | | Reset password | `ResetPasswordPage` | `resetPasswordAction` | | Confirm email | `ConfirmEmailPage` | `verifyEmailAction` | | Logout | `LogoutPage` | `logoutAction`, `setSessionCookie` | - **Components**: from `@zen/core/auth/components` - **Actions**: from `@zen/core/auth/actions` Create your own routes (e.g. `/login`, `/register`, `/auth/forgot`) and wrap Zen’s components in your layout. Each page follows the same pattern: a **server component** that loads data and passes actions, and a **client wrapper** that handles navigation and renders the Zen component. --- ## Route structure Choose a URL scheme and use it consistently. Two common options: **Option A – All under `/auth/*` (like the default)** `/auth/login`, `/auth/register`, `/auth/forgot`, `/auth/reset`, `/auth/confirm`, `/auth/logout` **Option B – Top-level routes** `/login`, `/register`, `/forgot`, `/reset`, `/confirm`, `/logout` The `onNavigate` callback receives one of: `'login' | 'register' | 'forgot' | 'reset'`. Map each to your chosen path, e.g. `router.push(\`/auth/${page}\`)` or `router.push(\`/${page}\`)`. Reset and confirm pages need `email` and `token` from the URL (e.g. `/auth/reset?email=...&token=...`). Your server page can read `searchParams` and pass them to the component. --- ## Component reference (props) Use this when wiring each custom page. | Component | Props | |-----------------------|--------| | **LoginPage** | `onSubmit` (loginAction), `onSetSessionCookie`, `onNavigate`, `redirectAfterLogin`, `currentUser` | | **RegisterPage** | `onSubmit` (registerAction), `onNavigate`, `currentUser` | | **ForgotPasswordPage**| `onSubmit` (forgotPasswordAction), `onNavigate`, `currentUser` | | **ResetPasswordPage** | `onSubmit` (resetPasswordAction), `onNavigate`, `email`, `token` (from URL) | | **ConfirmEmailPage** | `onSubmit` (verifyEmailAction), `onNavigate`, `email`, `token` (from URL) | | **LogoutPage** | `onLogout` (logoutAction), `onSetSessionCookie` (optional) | --- ## 1. Login **Server:** `app/login/page.js` (or `app/auth/login/page.js`) ```js import { getSession, loginAction, setSessionCookie } from '@zen/core/auth/actions'; import { LoginPageWrapper } from './LoginPageWrapper'; export default async function LoginRoute() { const session = await getSession(); return ( ); } ``` **Client:** `app/login/LoginPageWrapper.js` ```js 'use client'; import { useRouter } from 'next/navigation'; import { LoginPage } from '@zen/core/auth/components'; export function LoginPageWrapper({ loginAction, setSessionCookie, currentUser }) { const router = useRouter(); return ( router.push(`/auth/${page}`)} redirectAfterLogin="/" currentUser={currentUser} /> ); } ``` --- ## 2. Register **Server:** `app/register/page.js` ```js import { getSession, registerAction } from '@zen/core/auth/actions'; import { RegisterPageWrapper } from './RegisterPageWrapper'; export default async function RegisterRoute() { const session = await getSession(); return ( ); } ``` **Client:** `app/register/RegisterPageWrapper.js` ```js 'use client'; import { useRouter } from 'next/navigation'; import { RegisterPage } from '@zen/core/auth/components'; export function RegisterPageWrapper({ registerAction, currentUser }) { const router = useRouter(); return ( router.push(`/auth/${page}`)} currentUser={currentUser} /> ); } ``` --- ## 3. Forgot password **Server:** `app/forgot/page.js` ```js import { getSession, forgotPasswordAction } from '@zen/core/auth/actions'; import { ForgotPasswordPageWrapper } from './ForgotPasswordPageWrapper'; export default async function ForgotRoute() { const session = await getSession(); return ( ); } ``` **Client:** `app/forgot/ForgotPasswordPageWrapper.js` ```js 'use client'; import { useRouter } from 'next/navigation'; import { ForgotPasswordPage } from '@zen/core/auth/components'; export function ForgotPasswordPageWrapper({ forgotPasswordAction, currentUser }) { const router = useRouter(); return ( router.push(`/auth/${page}`)} currentUser={currentUser} /> ); } ``` --- ## 4. Reset password Requires `email` and `token` from the reset link (e.g. `/auth/reset?email=...&token=...`). Read `searchParams` in the server component and pass them to the client. **Server:** `app/auth/reset/page.js` (or `app/reset/page.js` with dynamic segment if needed) ```js import { resetPasswordAction } from '@zen/core/auth/actions'; import { ResetPasswordPageWrapper } from './ResetPasswordPageWrapper'; export default async function ResetRoute({ searchParams }) { const params = typeof searchParams?.then === 'function' ? await searchParams : searchParams ?? {}; const email = params.email ?? ''; const token = params.token ?? ''; return ( ); } ``` **Client:** `app/auth/reset/ResetPasswordPageWrapper.js` ```js 'use client'; import { useRouter } from 'next/navigation'; import { ResetPasswordPage } from '@zen/core/auth/components'; export function ResetPasswordPageWrapper({ resetPasswordAction, email, token }) { const router = useRouter(); return ( router.push(`/auth/${page}`)} email={email} token={token} /> ); } ``` --- ## 5. Confirm email Requires `email` and `token` from the verification link (e.g. `/auth/confirm?email=...&token=...`). **Server:** `app/auth/confirm/page.js` ```js import { verifyEmailAction } from '@zen/core/auth/actions'; import { ConfirmEmailPageWrapper } from './ConfirmEmailPageWrapper'; export default async function ConfirmRoute({ searchParams }) { const params = typeof searchParams?.then === 'function' ? await searchParams : searchParams ?? {}; const email = params.email ?? ''; const token = params.token ?? ''; return ( ); } ``` **Client:** `app/auth/confirm/ConfirmEmailPageWrapper.js` ```js 'use client'; import { useRouter } from 'next/navigation'; import { ConfirmEmailPage } from '@zen/core/auth/components'; export function ConfirmEmailPageWrapper({ verifyEmailAction, email, token }) { const router = useRouter(); return ( router.push(`/auth/${page}`)} email={email} token={token} /> ); } ``` --- ## 6. Logout **Server:** `app/auth/logout/page.js` ```js import { logoutAction, setSessionCookie } from '@zen/core/auth/actions'; import { LogoutPageWrapper } from './LogoutPageWrapper'; export default function LogoutRoute() { return ( ); } ``` **Client:** `app/auth/logout/LogoutPageWrapper.js` ```js 'use client'; import { LogoutPage } from '@zen/core/auth/components'; export function LogoutPageWrapper({ logoutAction, setSessionCookie }) { return ( ); } ``` --- ## Protecting routes Use `protect()` from `@zen/core/auth` and set `redirectTo` to your custom login path: ```js import { protect } from '@zen/core/auth'; export const middleware = protect({ redirectTo: '/login' }); ``` So unauthenticated users are sent to your custom login page. --- ## Default auth page If you don’t need a custom layout, keep using the built-in auth UI. In `app/auth/[...auth]/page.js`: ```js export { default } from '@zen/core/auth/page'; ``` This serves login, register, forgot, reset, confirm, and logout under `/auth/*` with the default styling.