Pages Documentation
Overview
The pages directory (src/pages/) follows the Next.js Pages Router convention where files automatically become routes. This is the application’s routing layer that maps URLs to React components.
Directory Structure
src/pages/
├── 404.tsx # Custom 404 error page
├── _app.tsx # Global app wrapper
├── _document.tsx # HTML document customization
├── _error.tsx # Error boundary page
├── api/ # API routes (NextAuth)
│ └── auth/
│ └── [...nextauth].ts # NextAuth catch-all route
├── content.tsx # Content management page
├── dashboard/
│ └── index.tsx # Main dashboard
├── index.tsx # Home/Login page (root)
└── reset/
└── [password].tsx # Password reset pageRoute Mapping
| File | Route | Purpose |
|---|---|---|
index.tsx | / | Login page with workspace routing |
dashboard/index.tsx | /dashboard | Main application dashboard |
content.tsx | /content | Content management |
reset/[password].tsx | /reset/:password | Password reset with token |
api/auth/[...nextauth].ts | /api/auth/* | NextAuth endpoints |
404.tsx | /* (404s) | Not found page |
Global Pages
_app.tsx (App Component)
The root component that wraps all pages. Handles global initialization.
Responsibilities:
- React Query provider setup
- NextAuth session provider
- Global styles (globals.css)
- Analytics initialization
- Toast provider
- Error boundary setup
Structure:
export default function App({
Component,
pageProps: { session, ...pageProps },
}: AppProps) {
return (
<SessionProvider session={session}>
<QueryClientProvider client={queryClient}>
<ThemeProvider>
<Toaster />
<Component {...pageProps} />
<Analytics />
</ThemeProvider>
</QueryClientProvider>
</SessionProvider>
);
}_document.tsx (Document Customization)
Customizes the HTML document structure.
Responsibilities:
- HTML lang attribute
- Meta tags injection
- Font preloading
- Third-party script loading (Sentry, GA)
_error.tsx (Error Page)
Custom error page for runtime errors.
Features:
- Error display
- Retry functionality
- Error reporting to Sentry
Main Pages
Home Page (index.tsx) - /
The entry point for all users. Handles authentication and workspace routing.
Features:
- Multiple sign-in forms based on workspace parameter
- Server-side session check (redirects if authenticated)
- Workspace query parameter handling
- reCAPTCHA integration
Sign-in Variants:
| Workspace | Form Component | User Type |
|---|---|---|
| (none) | SignInForm | Broker |
BeneficiaryEnrollment | BeneficiarySignInForm | Beneficiary |
Principal | PrincipalPortalSignInForm | Principal |
principalPortal_* | PrincipalPortalSignInForm | Principal (with provider) |
openEnrollment_* | SignInForm | Broker (open enrollment) |
Server-Side Props:
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const session = await getServerSession(ctx);
if (session?.user) {
// Redirect to dashboard with query params preserved
return {
redirect: {
destination: '/dashboard?workspace=...',
permanent: true,
},
};
}
return { props: {} };
};URL Parameters:
?workspace=- Determines which portal/sign-in form to show?email=- Pre-filled email (for password reset flows)
Dashboard Page (dashboard/index.tsx) - /dashboard
The main application dashboard after authentication.
Features:
- User type detection (Broker, Principal, Beneficiary)
- Insurance provider detection
- Workspace-based routing
- Auto-logout functionality
- Tab-based navigation
Server-Side Props:
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const session = await getServerSession(ctx);
if (!session) {
return { redirect: { destination: '/', permanent: false } };
}
return {
props: {
userType: session.user.type,
provider: session.user.insuranceProvider,
},
};
};Dashboard Variants:
| User Type | Provider | Display |
|---|---|---|
Broker | Any | BrokerTabs with full access |
Principal | Any | HRTabs with HR portal |
Beneficiary | Any | BeneficiaryPortal |
| Any | principalPortal_* | PrincipalPortal table |
| Any | openEnrollment_* | OpenEnrollmentTable |
State Management:
const [isClosed, setIsClosed] = useState(false);
const { isLoading } = useAutoLogout(); // Auto-logout after inactivityContent Page (content.tsx) - /content
Content management page for rich text content.
Features:
- BlockNote rich text editor integration
- Content creation and editing
- Markdown/HTML export
404 Page (404.tsx) - /*
Custom not found page.
Features:
- Howden branding
- Navigation back to dashboard
NotFoundillustration component
Dynamic Routes
Password Reset (reset/[password].tsx) - /reset/:token
Password reset page with token validation.
Route Parameter:
password- Reset token from email
Features:
- Token validation
- New password form
- Expired token handling
Usage:
// URL: /reset/abc123xyz
const router = useRouter();
const { password } = router.query; // 'abc123xyz'API Routes
NextAuth API (api/auth/[...nextauth].ts)
NextAuth.js catch-all route for authentication endpoints.
Handles:
/api/auth/signin- Sign in/api/auth/signout- Sign out/api/auth/session- Get session/api/auth/csrf- CSRF token/api/auth/providers- List providers/api/auth/callback/credentials- Credentials callback
Configuration:
import NextAuth from 'next-auth';
import { authOptions } from '@/lib/auth-options';
export default NextAuth(authOptions);Page Patterns
Server-Side Authentication
All protected pages should implement server-side session checking:
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const session = await getServerSession(ctx);
if (!session) {
return {
redirect: {
destination: '/',
permanent: false,
},
};
}
return {
props: {
user: session.user,
},
};
};Client-Side Session
Use useSession for client-side session data:
import { useSession } from 'next-auth/react';
function MyPage() {
const { data: session, status } = useSession();
if (status === 'loading') return <Loading />;
if (status === 'unauthenticated') return <SignInPrompt />;
return <div>Welcome, {session.user.name}</div>;
}Workspace Routing
Handle workspace parameters for portal routing:
const router = useRouter();
const { workspace } = router.query;
// Determine view based on workspace
const getViewComponent = () => {
switch (workspace) {
case 'BeneficiaryEnrollment':
return <BeneficiaryView />;
case 'Principal':
return <PrincipalView />;
default:
return <BrokerView />;
}
};Creating New Pages
Simple Page
// pages/new-feature.tsx
import { GetServerSideProps } from 'next';
import { getServerSession } from '@/utils/get-server-session';
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const session = await getServerSession(ctx);
if (!session) {
return { redirect: { destination: '/', permanent: false } };
}
return { props: {} };
};
export default function NewFeaturePage() {
return (
<div>
<h1>New Feature</h1>
{/* Content */}
</div>
);
}Nested Route
// pages/settings/profile.tsx
// Route: /settings/profile
import { ContentLayout } from '@/components/layouts/ContentLayout';
export default function ProfileSettingsPage() {
return (
<ContentLayout
left={<SettingsSidebar />}
right={<ProfileForm />}
/>
);
}Dynamic Route
// pages/members/[id].tsx
// Route: /members/123
import { useRouter } from 'next/router';
export default function MemberDetailPage() {
const router = useRouter();
const { id } = router.query;
return <MemberDetail memberId={id as string} />;
}Best Practices
- Always protect pages with
getServerSessioncheck - Preserve query params when redirecting
- Use Layout components for consistent page structure
- Implement loading states for client-side data fetching
- Handle errors gracefully with error boundaries
- Set page titles using
next/head
Page Title Pattern
import Head from 'next/head';
import { getPageTitle } from '@/constants/title';
export default function MyPage() {
return (
<>
<Head>
<title>{getPageTitle('MY_PAGE')}</title>
</Head>
{/* Page content */}
</>
);
}Loading State Pattern
import { useSession } from 'next-auth/react';
import { Skeleton } from '@/components/ui/Skeleton';
export default function MyPage() {
const { status } = useSession();
if (status === 'loading') {
return (
<div className="p-8">
<Skeleton className="h-8 w-48 mb-4" />
<Skeleton className="h-32 w-full" />
</div>
);
}
return <ActualContent />;
}