Update all references across source files, documentation, and configuration to reflect the new package scope and name. This includes updating `.npmrc` registry config, install instructions, module examples, and all import path comments throughout the codebase.
9.4 KiB
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.
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}`)orrouter.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)
import { getSession, loginAction, setSessionCookie } from '@zen/core/auth/actions';
import { LoginPageWrapper } from './LoginPageWrapper';
export default async function LoginRoute() {
const session = await getSession();
return (
<YourSiteLayout>
<LoginPageWrapper
loginAction={loginAction}
setSessionCookie={setSessionCookie}
currentUser={session?.user ?? null}
/>
</YourSiteLayout>
);
}
Client: app/login/LoginPageWrapper.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 (
<LoginPage
onSubmit={loginAction}
onSetSessionCookie={setSessionCookie}
onNavigate={(page) => router.push(`/auth/${page}`)}
redirectAfterLogin="/"
currentUser={currentUser}
/>
);
}
2. Register
Server: app/register/page.js
import { getSession, registerAction } from '@zen/core/auth/actions';
import { RegisterPageWrapper } from './RegisterPageWrapper';
export default async function RegisterRoute() {
const session = await getSession();
return (
<YourSiteLayout>
<RegisterPageWrapper
registerAction={registerAction}
currentUser={session?.user ?? null}
/>
</YourSiteLayout>
);
}
Client: app/register/RegisterPageWrapper.js
'use client';
import { useRouter } from 'next/navigation';
import { RegisterPage } from '@zen/core/auth/components';
export function RegisterPageWrapper({ registerAction, currentUser }) {
const router = useRouter();
return (
<RegisterPage
onSubmit={registerAction}
onNavigate={(page) => router.push(`/auth/${page}`)}
currentUser={currentUser}
/>
);
}
3. Forgot password
Server: app/forgot/page.js
import { getSession, forgotPasswordAction } from '@zen/core/auth/actions';
import { ForgotPasswordPageWrapper } from './ForgotPasswordPageWrapper';
export default async function ForgotRoute() {
const session = await getSession();
return (
<YourSiteLayout>
<ForgotPasswordPageWrapper
forgotPasswordAction={forgotPasswordAction}
currentUser={session?.user ?? null}
/>
</YourSiteLayout>
);
}
Client: app/forgot/ForgotPasswordPageWrapper.js
'use client';
import { useRouter } from 'next/navigation';
import { ForgotPasswordPage } from '@zen/core/auth/components';
export function ForgotPasswordPageWrapper({ forgotPasswordAction, currentUser }) {
const router = useRouter();
return (
<ForgotPasswordPage
onSubmit={forgotPasswordAction}
onNavigate={(page) => 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)
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 (
<YourSiteLayout>
<ResetPasswordPageWrapper
resetPasswordAction={resetPasswordAction}
email={email}
token={token}
/>
</YourSiteLayout>
);
}
Client: app/auth/reset/ResetPasswordPageWrapper.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 (
<ResetPasswordPage
onSubmit={resetPasswordAction}
onNavigate={(page) => 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
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 (
<YourSiteLayout>
<ConfirmEmailPageWrapper
verifyEmailAction={verifyEmailAction}
email={email}
token={token}
/>
</YourSiteLayout>
);
}
Client: app/auth/confirm/ConfirmEmailPageWrapper.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 (
<ConfirmEmailPage
onSubmit={verifyEmailAction}
onNavigate={(page) => router.push(`/auth/${page}`)}
email={email}
token={token}
/>
);
}
6. Logout
Server: app/auth/logout/page.js
import { logoutAction, setSessionCookie } from '@zen/core/auth/actions';
import { LogoutPageWrapper } from './LogoutPageWrapper';
export default function LogoutRoute() {
return (
<YourSiteLayout>
<LogoutPageWrapper
logoutAction={logoutAction}
setSessionCookie={setSessionCookie}
/>
</YourSiteLayout>
);
}
Client: app/auth/logout/LogoutPageWrapper.js
'use client';
import { LogoutPage } from '@zen/core/auth/components';
export function LogoutPageWrapper({ logoutAction, setSessionCookie }) {
return (
<LogoutPage
onLogout={logoutAction}
onSetSessionCookie={setSessionCookie}
/>
);
}
Protecting routes
Use protect() from @zen/core/auth and set redirectTo to your custom login path:
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:
export { default } from '@zen/core/auth/page';
This serves login, register, forgot, reset, confirm, and logout under /auth/* with the default styling.