Skip to Content

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 page

Route Mapping

FileRoutePurpose
index.tsx/Login page with workspace routing
dashboard/index.tsx/dashboardMain application dashboard
content.tsx/contentContent management
reset/[password].tsx/reset/:passwordPassword 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:

WorkspaceForm ComponentUser Type
(none)SignInFormBroker
BeneficiaryEnrollmentBeneficiarySignInFormBeneficiary
PrincipalPrincipalPortalSignInFormPrincipal
principalPortal_*PrincipalPortalSignInFormPrincipal (with provider)
openEnrollment_*SignInFormBroker (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 TypeProviderDisplay
BrokerAnyBrokerTabs with full access
PrincipalAnyHRTabs with HR portal
BeneficiaryAnyBeneficiaryPortal
AnyprincipalPortal_*PrincipalPortal table
AnyopenEnrollment_*OpenEnrollmentTable

State Management:

const [isClosed, setIsClosed] = useState(false); const { isLoading } = useAutoLogout(); // Auto-logout after inactivity

Content 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
  • NotFound illustration 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

  1. Always protect pages with getServerSession check
  2. Preserve query params when redirecting
  3. Use Layout components for consistent page structure
  4. Implement loading states for client-side data fetching
  5. Handle errors gracefully with error boundaries
  6. 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 />; }

Last updated on