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
- Use Server Components by default - Only use 'use client' when needed
- Check authentication early - Redirect before rendering protected content
- Handle loading and errors - Add loading.tsx and error.tsx
- Use layouts for shared UI - Avoid duplicating sidebar/navigation
- 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
Related Documentation
- Adding API Routes - Create backend endpoints
- Custom Components - Build reusable components
- Architecture Overview - System design