Customization

Adding Pages

How to create new pages, add routes, and integrate them into the sidebar navigation.

How File-Based Routing Works

Next.js uses file-based routing. Every page.tsx file becomes a route. There is no manual router configuration. The folder path determines the URL:

File PathURL
app/dashboard/reports/page.tsx/dashboard/reports
app/orders/[id]/page.tsx/orders/123
app/orders/[id]/edit/page.tsx/orders/123/edit
app/page.tsx/

Creating a New Dashboard Page

Create a folder at src/app/(dashboard)/dashboard/reports/ and add page.tsx. The page will immediately be available at /dashboard/reports wrapped in the dashboard layout (sidebar + header):

// src/app/(dashboard)/dashboard/reports/page.tsx
import { PageHeader } from '@/components/shared/page-header'
import { GlassCard } from '@/components/shared/glass-card'

export default function ReportsPage() {
  return (
    <div>
      <PageHeader title="Reports" description="View analytics reports" />

      <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
        <GlassCard title="Monthly Revenue" hoverable>
          <p className="text-sm text-[var(--haze-text-secondary)]">
            Revenue breakdown for the current month.
          </p>
        </GlassCard>

        <GlassCard title="User Growth" hoverable>
          <p className="text-sm text-[var(--haze-text-secondary)]">
            New user signups over time.
          </p>
        </GlassCard>
      </div>
    </div>
  )
}

Dynamic Routes

Use folders wrapped in square brackets for dynamic route segments. The parameter is passed as the params prop (a Promise in Next.js 15+):

// File: src/app/(dashboard)/orders/[id]/page.tsx
// URL:  /orders/123

import { PageHeader } from '@/components/shared/page-header'
import { GlassCard } from '@/components/shared/glass-card'

export default async function OrderDetailPage({
  params,
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params

  return (
    <div>
      <PageHeader title={`Order #${id}`} />
      <GlassCard>
        <p className="text-sm text-[var(--haze-text-secondary)]">
          Order details for {id}
        </p>
      </GlassCard>
    </div>
  )
}

Choosing a Layout

Pages automatically inherit the layout of whatever route group they live in. Place your file inside (dashboard) for sidebar chrome, (auth) for the auth shell, or (marketing) for the public navbar + footer.

Adding to Sidebar Navigation

To show your new page in the sidebar, open src/lib/navigation.ts and add an entry to the appropriate navigation group:

// src/lib/navigation.ts
export const navigation = [
  {
    label: 'Dashboards',
    items: [
      { label: 'Overview',  icon: 'LayoutDashboard', to: '/dashboard' },
      { label: 'Analytics', icon: 'BarChart3',       to: '/dashboard/analytics' },
      // Add your new page here:
      { label: 'Reports',   icon: 'FileBarChart',    to: '/dashboard/reports' },
    ],
  },
  // ... other groups
]

Icons come from lucide-react. The sidebar renders all groups and items automatically.

Fetching Data

Server components can fetch() directly. For interactive UIs (search, pagination, filters) use a client component with useEffect or a library like SWR:

'use client'
import { useState, useEffect } from 'react'

export default function OrdersList() {
  const [search, setSearch] = useState('')
  const [data, setData] = useState<any>(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    setLoading(true)
    const params = new URLSearchParams({ page: '1', per_page: '10', search })
    fetch(`/api/orders?${params}`)
      .then(r => r.json())
      .then(d => { setData(d); setLoading(false) })
  }, [search])

  return (
    <div>
      <input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search..." />
      {loading ? <div>Loading...</div> : (
        <div>
          {data?.data.map((order: any) => (
            <div key={order.id}>{order.orderNumber}</div>
          ))}
        </div>
      )}
    </div>
  )
}

The mock API routes at /api/* support query parameters for page, per_page, search, status, sort, and order.

Tip

Prefer server components by default — they ship zero JS to the browser. Only add 'use client' when you need state, effects, or browser-only APIs.

Next Steps

Learn how to customize the look and feel in the Theming guide, or see all available layouts in the Layouts reference.