Skip to main content

Adding Pages

Learn how to add new pages to your vertical application.

Basic Page

Create a new page by adding a page.tsx file in the app router:

// src/app/(presentation)/dashboard/page.tsx
export default function DashboardPage() {
return (
<div className="container mx-auto py-8">
<h1 className="text-2xl font-bold">Dashboard</h1>
<p>Welcome to your dashboard.</p>
</div>
);
}

Access at: http://localhost:3000/dashboard

Page with Authentication

Most pages require user authentication:

// src/app/(presentation)/dashboard/page.tsx
import { auth } from '@/auth';
import { redirect } from 'next/navigation';

export default async function DashboardPage() {
const session = await auth();

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

return (
<div className="container mx-auto py-8">
<h1 className="text-2xl font-bold">
Welcome, {session.user?.name}
</h1>
</div>
);
}

Tenant-Aware Page

Access the current tenant configuration:

// src/app/(presentation)/dashboard/page.tsx
import { getTenantConfig } from '@/eai.config';
import { headers } from 'next/headers';

export default async function DashboardPage() {
// Get tenant from query parameter or header
const headersList = headers();
const tenantId = headersList.get('x-tenant-id') || 'template';
const config = getTenantConfig(tenantId);

return (
<div className="container mx-auto py-8">
<h1 className="text-2xl font-bold">
{config.displayName} Dashboard
</h1>
</div>
);
}

Page with Store Access

Use client components to access the Zustand store:

// src/app/(presentation)/dashboard/page.tsx
import { DashboardContent } from './DashboardContent';

export default function DashboardPage() {
return (
<div className="container mx-auto py-8">
<DashboardContent />
</div>
);
}
// src/app/(presentation)/dashboard/DashboardContent.tsx
'use client';

import { useStoreValue } from '@enterpriseaigroup/client';

export function DashboardContent() {
const userName = useStoreValue('user.name');
const projects = useStoreValue('projects.list');

return (
<div>
<h1>Welcome, {userName || 'Guest'}</h1>
<p>You have {projects?.length || 0} projects.</p>
</div>
);
}

Dynamic Routes

Create pages with URL parameters:

// src/app/(presentation)/projects/[id]/page.tsx
import { getProject } from '@/lib/projects';

interface Props {
params: { id: string };
}

export default async function ProjectPage({ params }: Props) {
const project = await getProject(params.id);

if (!project) {
return <div>Project not found</div>;
}

return (
<div className="container mx-auto py-8">
<h1 className="text-2xl font-bold">{project.name}</h1>
<p>{project.description}</p>
</div>
);
}

Access at: http://localhost:3000/projects/abc123

Nested Layouts

Create shared layouts for groups of pages:

// src/app/(presentation)/settings/layout.tsx
import { SettingsSidebar } from '@/components/SettingsSidebar';

export default function SettingsLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex">
<SettingsSidebar />
<main className="flex-1 p-8">
{children}
</main>
</div>
);
}
// src/app/(presentation)/settings/profile/page.tsx
export default function ProfilePage() {
return <h1>Profile Settings</h1>;
}

// src/app/(presentation)/settings/security/page.tsx
export default function SecurityPage() {
return <h1>Security Settings</h1>;
}

Loading States

Add loading UI while the page loads:

// src/app/(presentation)/dashboard/loading.tsx
export default function Loading() {
return (
<div className="container mx-auto py-8">
<div className="animate-pulse">
<div className="h-8 bg-gray-200 rounded w-1/4 mb-4" />
<div className="h-4 bg-gray-200 rounded w-1/2" />
</div>
</div>
);
}

Error Handling

Add error boundaries for graceful failures:

// src/app/(presentation)/dashboard/error.tsx
'use client';

export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div className="container mx-auto py-8">
<h2 className="text-xl font-bold text-red-600">
Something went wrong
</h2>
<p className="mb-4">{error.message}</p>
<button
onClick={reset}
className="px-4 py-2 bg-primary text-white rounded"
>
Try again
</button>
</div>
);
}

Page Metadata

Set page title and meta tags:

// src/app/(presentation)/dashboard/page.tsx
import { Metadata } from 'next';

export const metadata: Metadata = {
title: 'Dashboard | Vertical Template',
description: 'Your personal dashboard',
};

export default function DashboardPage() {
// ...
}

Dynamic metadata:

// src/app/(presentation)/projects/[id]/page.tsx
import { Metadata } from 'next';
import { getProject } from '@/lib/projects';

interface Props {
params: { id: string };
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const project = await getProject(params.id);

return {
title: `${project?.name || 'Project'} | Vertical Template`,
description: project?.description,
};
}

Protecting Routes with Middleware

For protecting multiple pages, use middleware:

// src/middleware.ts
import { auth } from '@/auth';
import { NextResponse } from 'next/server';

export default auth((req) => {
if (!req.auth) {
return NextResponse.redirect(new URL('/api/auth/signin', req.url));
}
});

export const config = {
matcher: [
'/dashboard/:path*',
'/settings/:path*',
'/projects/:path*',
],
};

Best Practices

  1. Use Server Components by default - Only use 'use client' when needed
  2. Check authentication early - Redirect before rendering protected content
  3. Handle loading and errors - Add loading.tsx and error.tsx
  4. Use layouts for shared UI - Avoid duplicating sidebar/navigation
  5. Set metadata - Improve SEO and user experience

File Structure Example

src/app/(presentation)/
├── layout.tsx # Shared layout for all pages
├── page.tsx # Home page (/)
├── loading.tsx # Global loading state
├── error.tsx # Global error boundary

├── dashboard/
│ ├── page.tsx # /dashboard
│ ├── loading.tsx # Dashboard loading state
│ └── DashboardContent.tsx # Client component

├── projects/
│ ├── page.tsx # /projects
│ └── [id]/
│ ├── page.tsx # /projects/:id
│ └── edit/
│ └── page.tsx # /projects/:id/edit

└── settings/
├── layout.tsx # Settings sidebar layout
├── page.tsx # /settings (redirects to profile)
├── profile/
│ └── page.tsx # /settings/profile
└── security/
└── page.tsx # /settings/security