Skip to main content

Adding Pages

Create a new route that fits the existing layout.

DirectoryKit uses Next.js route groups (folders in parentheses) to apply shared layouts. Adding a new page = adding one page.tsx file under the right group.

Pick the right route group

GroupLayoutUse for
(marketing)/Header + Footer + AdBannerPublic pages — blog, pricing, FAQ, about
(dashboard)/Header + user dashboard layoutLogged-in user pages
(admin)/admin/Admin sidebar layoutAdmin-only pages

The parentheses in the folder name are stripped from the URL — they only exist to group by layout.

Example: add a public /roadmap page

1. Create the file

app/(marketing)/roadmap/page.tsx

2. Write the component

app/(marketing)/roadmap/page.tsx
import type { Metadata } from 'next';
import { siteConfig } from '@/config/site.config';
 
export const metadata: Metadata = {
  title: `Roadmap — ${siteConfig.name}`,
  description: 'What we are building next.',
};
 
export default function RoadmapPage() {
  return (
    <div className="mx-auto max-w-3xl px-6 py-16">
      <h1 className="text-4xl font-bold">Roadmap</h1>
      <p className="mt-4 text-muted-foreground">
        Features we're working on.
      </p>
      <ul className="mt-8 space-y-4">
        <li>Q2: AI-powered search</li>
        <li>Q3: Mobile app</li>
      </ul>
    </div>
  );
}

If you want the page in the header or footer, edit components/layout/Header.tsx or components/layout/Footer.tsx and add your entry to the nav array.

Example: add a logged-in /invites page

1. Create the file

app/(dashboard)/invites/page.tsx

2. Protect it

import { requireAuth, getCurrentUser } from '@/lib/supabase/auth-helpers';
 
export default async function InvitesPage() {
  const redirect = await requireAuth();
  if (redirect) return redirect;
 
  const user = await getCurrentUser();
 
  return (
    <div>
      <h1>Your invites, {user!.email}</h1>
    </div>
  );
}

Example: add an admin page

1. Create the file

app/(admin)/admin/reports/page.tsx

2. Protect it

import { isAdmin, requireAuth } from '@/lib/supabase/auth-helpers';
import { notFound } from 'next/navigation';
 
export default async function AdminReportsPage() {
  const redirect = await requireAuth();
  if (redirect) return redirect;
 
  if (!(await isAdmin())) notFound();
 
  return <div>Admin reports...</div>;
}

Appears at /admin/reports. Won't show up for non-admins.

Pages that need data

Use the db helper for server-side data fetching:

import { db } from '@/lib/supabase/database';
 
export default async function LiveAppsPage() {
  const apps = await db.find('apps',
    { status: 'live' },
    { sort: { upvotes: -1 }, limit: 20 }
  );
 
  return (
    <ul>
      {apps.map((app) => <li key={app.id}>{app.name}</li>)}
    </ul>
  );
}

Adding an API route

If your page needs data mutations, pair it with an API route at app/api/your-thing/route.ts — see API Reference Overview for the template.

Docs page (this site)

If you want a new page in this documentation:

  1. Create content/docs/<section>/<page>.mdx with frontmatter
  2. Add an entry to lib/docs/navigation.ts

With AI

AI Prompt· Add a new page

Add a new page at route \{/your-url-path\} to this DirectoryKit project.

Details:

  • Purpose: {what the page is for — e.g. "a public roadmap", "a logged-in invites page", "an admin reports page"}
  • Access: {public | logged-in-only | admin-only}
  • Content sections: {list what the page shows — e.g. "a hero, a 3-column feature grid, a FAQ"}
  • Data source: {static content | Supabase table "X" via db.find | Stripe API | none}

Follow CLAUDE.md conventions:

  • Pick the right route group ((marketing) / (dashboard) / (admin)).
  • Use @/ alias for imports.
  • Use siteConfig for brand name in metadata.
  • For protected pages, use requireAuth() / isAdmin() from @/lib/supabase/auth-helpers.
  • For DB queries, use the db singleton from @/lib/supabase/database.
  • Use shadcn/ui components from @/components/ui, not raw HTML.

Also add a nav link if appropriate (Header.tsx or Footer.tsx).

See also