Adding a New Page
Step-by-step guide to adding authenticated dashboard pages and public marketing pages in Next Starter, including metadata, sidebar navigation, and breadcrumb support.
The project uses Next.js App Router with two distinct layout trees: app/(site)/ for marketing pages and app/dashboard/ for the authenticated user area. Each layout handles its own navigation, authentication, and styling.
Dashboard Pages
How the Layout Works
Every file inside app/dashboard/ is automatically wrapped by app/dashboard/layout.tsx. That layout:
- Fetches the session with
getSession()and redirects unauthenticated users to/auth/sign-in - Renders the sidebar, mobile header, and breadcrumb navigation
- Provides the
SubscriptionProviderandSidebarProvidercontext
You do not need to add authentication logic to individual dashboard pages unless you need the user's ID for database queries. In that case, call getSession() and handle the redirect defensively.
Create the Page File
Create a new file at the appropriate path inside app/dashboard/. For a page at /dashboard/reports:
app/
└── dashboard/
└── reports/
└── page.tsximport type { Metadata } from "next";
import { redirect } from "next/navigation";
import { generateMeta } from "@/lib/config";
import { getSession } from "@/lib/server/auth-helpers";
export const metadata: Metadata = generateMeta({
title: "Reports",
description: "View your usage reports.",
pathname: "/dashboard/reports",
noIndex: true,
});
export default async function ReportsPage() {
const session = await getSession();
if (!session) redirect("/auth/sign-in");
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-semibold">Reports</h1>
<p className="text-muted-foreground mt-1">Your usage over time.</p>
</div>
{/* page content */}
</div>
);
}Use generateMeta from lib/config for consistent metadata. Pass noIndex: true for all dashboard pages to exclude them from search engine indexing.
Add a Sidebar Link
Open components/dashboard/sidebar.tsx and add an entry to navigationItems. Choose an icon from lucide-react and assign it to one of three groups:
| Group | Purpose |
|---|---|
"main" | Primary navigation visible to all users |
"admin" | Only shown to users with role === "admin" |
"secondary" | Bottom of the sidebar (Billing, Settings) |
import { BarChart } from "lucide-react";
const navigationItems: NavigationItem[] = [
{ name: "Dashboard", href: "/dashboard", icon: Home, group: "main" },
{ name: "Reports", href: "/dashboard/reports", icon: BarChart, group: "main" }, // add this
// ... rest of items
];The sidebar uses pathname.startsWith(item.href + "/") for active state detection, so nested routes like /dashboard/reports/detail will also highlight the Reports link.
Nested Routes and Layouts
If your feature needs multiple sub-pages, add a nested layout:
app/
└── dashboard/
└── reports/
├── layout.tsx # optional nested layout
├── page.tsx # /dashboard/reports
└── [id]/
└── page.tsx # /dashboard/reports/[id]Access dynamic segment parameters by awaiting the params prop:
interface Props {
params: Promise<{ id: string }>;
}
export default async function ReportDetailPage({ params }: Props) {
const { id } = await params;
// fetch report by id
}Marketing Pages
How the Layout Works
Files inside app/(site)/ share the layout at app/(site)/layout.tsx. This layout renders the public navigation header and footer. The (site) folder name is a route group: it does not appear in the URL.
Create the Page File
For a new page at /case-studies:
app/
└── (site)/
└── case-studies/
└── page.tsximport type { Metadata } from "next";
import { generateMeta } from "@/lib/config";
export const metadata: Metadata = generateMeta({
title: "Case Studies",
description: "How teams use Next Starter to ship faster.",
pathname: "/case-studies",
});
export default function CaseStudiesPage() {
return (
<div className="bg-background">
<section className="py-16 sm:py-24">
<div className="mx-auto max-w-4xl px-6">
<h1 className="text-4xl font-semibold tracking-tight text-foreground">
Case Studies
</h1>
<p className="mt-4 text-lg text-muted-foreground">
Real-world examples of what you can build.
</p>
</div>
</section>
</div>
);
}Keep marketing pages as server components by default for better performance and SEO. Convert to client components only when you need interactivity.
Sections vs. Pages
The homepage at app/(site)/page.tsx composes several section components (Hero, LogoCloud, FeatureLeft, FeatureRight, Bento, FAQ, CallToAction) imported from the same directory. For complex marketing pages, follow the same pattern: build each visual section as its own component file and compose them in page.tsx.
import Hero from "./hero";
import LogoCloud from "./logo-cloud";
import FeatureLeft from "./feature-left";
import FeatureRight from "./feature-right";
import Bento from "./bento";
import FAQ from "./faq";
import CallToAction from "./call-to-action";
export default function Home() {
return (
<div className="bg-background">
<Hero />
<LogoCloud />
<FeatureLeft />
<FeatureRight />
<Bento />
<FAQ />
<CallToAction />
</div>
);
}How the Breadcrumb Works
The dashboard breadcrumb at components/dashboard/breadcrumb.tsx reads the current URL path and generates labels automatically. It splits the pathname on /, filters out dashboard, replaces dashes with spaces, and capitalizes each word. UUID-format segments are filtered out automatically. To use a custom label for a segment, update the displayName formatting logic inside the breadcrumb component.
Metadata
Both page types use generateMeta from lib/config.ts to produce consistent <title> and <meta> tags. The function merges your values with the site-wide defaults defined in APP_CONFIG.
export const metadata: Metadata = generateMeta({
title: "Page Title", // becomes "<title>Page Title - App Name</title>"
description: "...",
pathname: "/your-path", // used to build the canonical URL
noIndex: true, // adds <meta name="robots" content="noindex, nofollow">
});Pass noIndex: true for any page that should not appear in search results: all dashboard pages, auth pages, and any preview or staging content.
Build Your First SaaS Feature
Step-by-step tutorial for building an auth-protected dashboard feature using Prisma, server actions, and shadcn/ui, covering the full stack from database model to UI form.
Customizing the Theme
Update brand colors, switch fonts, configure dark mode, and add new Tailwind CSS design tokens using CSS custom properties.