Project Structure
The codebase uses Next.js route groups to separate marketing pages from the authenticated app.
Directory overview
app/
(marketing)/ # Public pages (home, pricing)
(app)/ # Protected pages (dashboard, billing, settings)
login/ # Sign-in page
api/ # API routes (auth, webhooks)
components/
app-shell.tsx # App layout with sidebar
ui/ # shadcn/ui components
db/
schema.ts # Drizzle table definitions
migrations/ # SQL migrations
lib/
auth.ts # NextAuth configuration
auth/helpers.ts # Auth utilities
auth/magic-link.ts # Magic link logic
billing/plans.ts # Plan definitions
subscriptions.ts # Subscription helpers
queries/ # Database query functions
email.ts # Email sending helpers
emails/ # React email templates
.cursor/
rules/ # Cursor rules
commands/ # Cursor commands
Route groups
(marketing) - Public pages
- Simple header + footer layout
- No authentication required
- Example:
/(home),/pricing
app/ - Protected pages (served at /app/*)
- Uses
AppShellcomponent with sidebar navigation - All pages protected via
requireUser()in layout - Example:
/app/dashboard,/app/billing,/app/settings
Root-level pages
/login- Sign-in page (no auth required)- API routes under
/api
Adding pages
Public marketing page
Create under app/(marketing)/:
app/(marketing)/about/page.tsx
// app/(marketing)/about/page.tsx
export default function AboutPage() {
return <div>About us</div>;
}
Protected app page
Use the /add-protected-page command, or manually:
- Create page under
app/app/[route]/page.tsx - Add navigation link in
components/app-shell.tsx
app/app/projects/page.tsx
// app/app/projects/page.tsx
import { requireUser } from "@/lib/auth/helpers";
export default async function ProjectsPage() {
const user = await requireUser();
return <div>Projects for {user.email}</div>;
}
Key conventions
- Route groups don't affect URLs:
(marketing)/pricing=/pricing - Protected pages always call
requireUser()or rely on layout auth - API routes live under
app/api/ - Shared components go in
components/ - Feature logic goes in
lib/ - Database queries go in
lib/queries/
Branding configuration
All branding is centralized in lib/config/site.ts:
export const siteConfig = {
name: "My SaaS",
tagline: "Ship faster",
description: "Your product description",
url: process.env.NEXT_PUBLIC_SITE_URL,
email: {
from: process.env.EMAIL_FROM_ADDRESS,
fromName: process.env.EMAIL_FROM_NAME,
support: "support@example.com",
},
logo: {
default: "/logo.svg",
dark: "/logo-dark.svg",
},
};
This config is used by:
- App shell header and navigation
- Marketing pages
- Email templates
- SEO metadata