| name | page-builder |
| description | Page builder for Sunrise. Creates new pages following Next.js 16
App Router patterns: route groups, layouts, metadata, async params,
Server Components by default, and the project's auth conventions.
Knows the four top-level URL segments — `(auth)`, `(protected)`,
`(public)` route groups plus the `admin/` regular folder. Use when
creating new pages, route groups, or admin sub-routes.
|
Page Builder Skill
Pages in Sunrise are organised by route group (no URL effect) and one regular folder (admin/, which does create URL segments). Always determine the correct location before creating the page.
URL-level layout of app/
| Path | Kind | URL effect | Auth | Use for |
|---|
app/(auth)/ | Route group | None (URLs at root) | No | Login, signup, password reset, email verification |
app/(protected)/ | Route group | None (URLs at root) | Yes | Dashboard, settings, profile, account |
app/(public)/ | Route group | None (URLs at root) | No | Marketing, about, pricing, public landing pages |
app/admin/ | Regular folder | Creates /admin/* | Admin | Admin dashboard and sub-routes (/admin/orchestration/...) |
app/api/ | Regular folder | Creates /api/* | Per-route | Handled by the api-builder skill |
Route groups ((name)) organise code without affecting URLs — app/(protected)/dashboard/page.tsx lives at /dashboard. Regular folders like admin/ do create URL segments — app/admin/orchestration/page.tsx lives at /admin/orchestration.
Decision tree
Need a new page?
│
├─ Is it an authentication flow? (login, signup, verify)
│ └─ YES → app/(auth)/<name>/page.tsx
│
├─ Is it an admin-only page or sub-route under /admin?
│ └─ YES → app/admin/<name>/page.tsx (regular folder, creates /admin/<name>)
│
├─ Does it require the user to be logged in?
│ └─ YES → app/(protected)/<name>/page.tsx
│
├─ Is it a public marketing or info page?
│ └─ YES → app/(public)/<name>/page.tsx
│
└─ Needs a completely different layout?
└─ YES → Create a new route group: app/(<name>)/layout.tsx + page.tsx
4-Step Workflow
Step 1: Analyze Requirements
Questions to answer:
- What is the page purpose?
- Which route group does it belong to?
- Does it need authentication?
- Does it need a new layout or the existing one?
- Will it contain forms that use
useSearchParams()?
- What metadata (title, description) should it have?
Step 2: Create Page File
File location: app/([group])/[name]/page.tsx
Server Component (default):
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Page Title',
description: 'Page description for SEO',
};
export default function PageName() {
return (
<div className="space-y-6">
<h1 className="text-2xl font-bold">Page Title</h1>
{/* Page content */}
</div>
);
}
Protected Page (with auth check):
import type { Metadata } from 'next';
import { getServerSession } from '@/lib/auth/utils';
import { clearInvalidSession } from '@/lib/auth/clear-session';
export const metadata: Metadata = {
title: 'Page Title - Sunrise',
description: 'Page description',
};
export default async function PageName() {
const session = await getServerSession();
if (!session) {
clearInvalidSession('/page-path');
}
const { user } = session;
return (
<div className="space-y-6">
<h1 className="text-2xl font-bold">Welcome, {user.name}</h1>
{/* Page content */}
</div>
);
}
Page with Form (needs Suspense):
import type { Metadata } from 'next';
import { Suspense } from 'react';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import { YourForm } from '@/components/forms/your-form';
export const metadata: Metadata = {
title: 'Page Title',
description: 'Page description',
};
export default function PageName() {
return (
<Card>
<CardHeader>
<CardTitle>Form Title</CardTitle>
</CardHeader>
<CardContent>
<Suspense fallback={<div>Loading...</div>}>
<YourForm />
</Suspense>
</CardContent>
</Card>
);
}
Step 3: Create Layout (if needed)
Only create if page needs different layout from existing route group.
Layout Template:
import type { Metadata, ReactNode } from 'next';
export const metadata: Metadata = {
title: {
template: '%s - Sunrise',
default: 'Section Name - Sunrise',
},
description: 'Section description',
};
export default function SectionLayout({
children,
}: Readonly<{ children: ReactNode }>) {
return (
<div className="min-h-screen">
{/* Layout structure */}
<main>{children}</main>
</div>
);
}
Step 4: Add Error Boundary (if new route group)
File: app/([group])/error.tsx
'use client';
import { useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { AlertTriangle, Home, RefreshCw } from 'lucide-react';
import { logger } from '@/lib/logging';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
logger.error('Page error', error, { digest: error.digest });
}, [error]);
return (
<div className="flex min-h-[400px] flex-col items-center justify-center space-y-4">
<AlertTriangle className="h-12 w-12 text-destructive" />
<h2 className="text-xl font-semibold">Something went wrong</h2>
{process.env.NODE_ENV === 'development' && (
<p className="text-muted-foreground max-w-md text-center text-sm">
{error.message}
</p>
)}
<div className="flex gap-2">
<Button onClick={reset} variant="outline">
<RefreshCw className="mr-2 h-4 w-4" />
Try again
</Button>
<Button asChild>
<a href="/">
<Home className="mr-2 h-4 w-4" />
Go home
</a>
</Button>
</div>
</div>
);
}
Page Type Templates
Protected Page (Dashboard Style)
import type { Metadata } from 'next';
import { getServerSession } from '@/lib/auth/utils';
import { clearInvalidSession } from '@/lib/auth/clear-session';
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card';
export const metadata: Metadata = {
title: 'Settings - Sunrise',
description: 'Manage your account settings',
};
export default async function SettingsPage() {
const session = await getServerSession();
if (!session) {
clearInvalidSession('/settings');
}
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-bold">Settings</h1>
<p className="text-muted-foreground">Manage your account preferences</p>
</div>
<Card>
<CardHeader>
<CardTitle>Account Settings</CardTitle>
<CardDescription>Update your account information</CardDescription>
</CardHeader>
<CardContent>
{/* Settings form or content */}
</CardContent>
</Card>
</div>
);
}
Auth Page (Login/Signup Style)
import type { Metadata } from 'next';
import { Suspense } from 'react';
import Link from 'next/link';
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card';
import { ForgotPasswordForm } from '@/components/forms/forgot-password-form';
export const metadata: Metadata = {
title: 'Forgot Password',
description: 'Reset your password',
};
export default function ForgotPasswordPage() {
return (
<Card>
<CardHeader className="space-y-1">
<CardTitle className="text-2xl">Forgot Password</CardTitle>
<CardDescription>
Enter your email and we'll send you a reset link
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<Suspense fallback={<div>Loading...</div>}>
<ForgotPasswordForm />
</Suspense>
<p className="text-muted-foreground text-center text-sm">
Remember your password?{' '}
<Link href="/login" className="text-primary hover:underline">
Sign in
</Link>
</p>
</CardContent>
</Card>
);
}
Public Page (Marketing Style)
import type { Metadata } from 'next';
import Link from 'next/link';
import { Button } from '@/components/ui/button';
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '@/components/ui/card';
import { Check } from 'lucide-react';
export const metadata: Metadata = {
title: 'Pricing - Sunrise',
description: 'Choose the plan that works for you',
};
export default function PricingPage() {
return (
<div className="container mx-auto px-4 py-16">
<div className="mb-12 text-center">
<h1 className="text-4xl font-bold">Simple, Transparent Pricing</h1>
<p className="text-muted-foreground mt-4 text-xl">
Choose the plan that works for you
</p>
</div>
<div className="grid gap-8 md:grid-cols-3">
{/* Pricing cards */}
</div>
</div>
);
}
Common Imports Reference
import type { Metadata } from 'next';
import Link from 'next/link';
import { useRouter, useSearchParams } from 'next/navigation';
import { Suspense } from 'react';
import { getServerSession } from '@/lib/auth/utils';
import { clearInvalidSession } from '@/lib/auth/clear-session';
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter,
} from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { AlertTriangle, Home, RefreshCw, Check, ArrowRight } from 'lucide-react';
import { logger } from '@/lib/logging';
Verification Checklist
Usage Examples
Protected settings page:
User: "Create a settings page for user preferences"
Assistant: [Creates app/(protected)/settings/page.tsx with auth check]
Public about page:
User: "Add an about page to the marketing section"
Assistant: [Creates app/(public)/about/page.tsx with marketing layout]
New admin section:
User: "Create an admin dashboard with different layout"
Assistant: [Creates app/(admin)/layout.tsx, error.tsx, and dashboard/page.tsx]