Project Structure
A reference for the Next Starter directory layout, every folder, key file, and the conventions that keep the codebase consistent as it grows.
This page documents how the project is organized. Read it before you start adding pages or features.
Directory Tree
app/
├── layout.tsx # Root layout: HTML shell, fonts, ThemeProvider, Toaster
├── globals.css # Global stylesheet (Tailwind v4, theme tokens)
├── (site)/ # Marketing pages (public, no auth required)
│ ├── page.tsx # Homepage: hero, logo cloud, features, bento, FAQ, CTA
│ ├── hero.tsx # Hero section (co-located)
│ ├── logo-cloud.tsx # Logo cloud section (co-located)
│ ├── feature-left.tsx # Feature section left layout (co-located)
│ ├── feature-right.tsx # Feature section right layout (co-located)
│ ├── bento.tsx # Bento grid component (co-located)
│ ├── faq.tsx # FAQ section component (co-located)
│ ├── call-to-action.tsx # CTA section (co-located)
│ ├── pricing/
│ │ ├── page.tsx # Standalone pricing page
│ │ └── quote.tsx # Pricing quote component (co-located)
│ ├── about/page.tsx
│ ├── contact/
│ │ ├── page.tsx # Contact page
│ │ └── contact-form.tsx # Contact form component (co-located)
│ ├── privacy/page.tsx
│ ├── terms/page.tsx
│ └── layout.tsx # Marketing layout (header, footer)
│
├── auth/ # Authentication flows
│ ├── layout.tsx # Auth layout (split-screen: card left, gradient right)
│ ├── sign-in/
│ │ ├── page.tsx
│ │ └── sign-in-form.tsx # Sign-in form (co-located)
│ ├── register/
│ │ ├── page.tsx
│ │ └── register-form.tsx # Registration form (co-located)
│ ├── forgot-password/
│ │ ├── page.tsx
│ │ └── forgot-password-form.tsx # Forgot password form (co-located)
│ ├── reset-password/
│ │ ├── page.tsx
│ │ └── reset-password-form.tsx # Reset password form (co-located)
│ ├── resend-verification/
│ │ ├── page.tsx
│ │ └── resend-verification-form.tsx # Resend verification form (co-located)
│ └── error/
│ ├── page.tsx
│ └── error-content.tsx # Error display (co-located)
│
├── dashboard/ # Protected user area (session required)
│ ├── page.tsx # Dashboard home
│ ├── dashboard-content.tsx # Dashboard content (co-located)
│ ├── loading.tsx # Dashboard loading skeleton
│ ├── layout.tsx # Dashboard layout (sidebar, breadcrumb, mobile header)
│ ├── billing/
│ │ ├── page.tsx # Stripe subscription management
│ │ ├── billing-content.tsx # Billing details (co-located)
│ │ ├── current-plan-card.tsx # Current plan display (co-located)
│ │ └── loading.tsx
│ ├── settings/
│ │ ├── page.tsx # Account settings
│ │ ├── settings-form.tsx # Settings form (co-located)
│ │ └── loading.tsx
│ ├── profile/ # User profile pages
│ │ ├── page.tsx # Profile overview
│ │ ├── profile-form.tsx # Profile edit form (co-located)
│ │ ├── loading.tsx
│ │ ├── change-email/
│ │ │ ├── page.tsx # Change email address
│ │ │ ├── change-email-form.tsx # Change email form (co-located)
│ │ │ └── loading.tsx
│ │ └── change-password/
│ │ ├── page.tsx # Change password
│ │ ├── change-password-form.tsx # Change password form (co-located)
│ │ └── loading.tsx
│ └── (admin)/ # Admin-only routes (redirects non-admins)
│ ├── layout.tsx # Enforces admin role check
│ ├── users/
│ │ ├── page.tsx # User management table
│ │ └── loading.tsx
│ └── files/
│ ├── page.tsx # File management
│ ├── files-content.tsx # File management UI (co-located)
│ └── loading.tsx
│
├── actions/ # Server Actions (mutations only)
│ ├── contact.ts # Contact form submission
│ ├── files.ts # File upload/delete
│ ├── recaptcha.ts # reCAPTCHA verification
│ ├── settings.ts # Account settings update
│ └── user.ts # User management (admin)
│
├── api/
│ ├── auth/[...all]/ # Better Auth API handler
│ │ └── route.ts
│ └── health/ # Health check for deployments
│ └── route.ts
│
├── error.tsx # Root error boundary
├── not-found.tsx # 404 page
├── manifest.ts # Web app manifest
├── robots.ts # robots.txt generation
└── sitemap.ts # Sitemap generation
components/
├── ui/ # shadcn/ui components
├── pricing/ # Pricing table component
├── dashboard/ # Dashboard chrome (sidebar, breadcrumb, mobile-header)
├── users/ # User management table and dialogs
├── auth/ # Auth components
│ └── google-signin-button.tsx
├── hero.tsx # Reusable hero section component
├── header.tsx # Site header
├── footer.tsx # Site footer
├── mobile-nav.tsx # Mobile navigation
├── theme-toggle.tsx # Dark/light mode toggle
├── theme-provider.tsx # Theme context provider
└── subscription-provider.tsx # Subscription context (wraps dashboard)
lib/
├── auth.ts # Better Auth server config + Stripe plugin
├── auth-client.ts # Better Auth client hooks + subscription methods
├── config.ts # APP_CONFIG, generateMeta, JSON-LD schemas
├── pricing.ts # Pricing tiers (single source of truth)
├── db.ts # Prisma client singleton
├── email.ts # SMTP2Go integration + HTML templates
├── logger.ts # Pino structured logger
├── utils.ts # cn(), formatDateLong(), and other utilities
├── navigation.ts # Navigation configuration
├── file-utils.ts # File operation helpers
├── client/ # Client-only utilities
│ ├── avatar.ts
│ └── image.ts
├── email/ # Email utilities
│ └── blocked-domains.json
├── validations/ # Zod 4 schemas
│ ├── auth.ts # Sign-in, sign-up, password reset schemas
│ ├── contact.ts # Contact form schema
│ ├── env.ts # Environment variable validation (startup)
│ ├── files.ts # File upload validation
│ ├── settings.ts # Settings form schema
│ └── user.ts # User management schemas
└── server/ # Server-only code
├── auth-helpers.ts # getSession() — cached per request
└── s3.ts # Cloudflare R2 file upload helpers
hooks/
└── use-mobile.ts # Mobile viewport detection hook
types/
└── api.ts # Shared API type definitions
prisma/
├── schema.prisma # Database schema
└── migrations/ # Applied database migrations
public/ # Static assets
├── og-image.png # 1200x630 Open Graph image
├── logo.svg
├── logo-dark.svg
├── icon.svg
├── icon-192.png
├── icon-512.png
├── icon-mask.png
├── apple-touch-icon.png
└── images/ # Optimized images (bento, features, logos, team, testimonials)Key Conventions
Route Groups: (site) and (admin)
Parentheses in directory names create route groups in Next.js. A route group does not affect the URL path. It only controls which routes share a layout.
(site) groups all marketing pages under app/(site)/layout.tsx, which applies the public header and footer. The URL for app/(site)/pricing/page.tsx is /pricing, not /site/pricing.
(admin) groups admin-only routes under app/dashboard/(admin)/. These routes inherit session protection from the parent app/dashboard/layout.tsx. The URL for app/dashboard/(admin)/users/page.tsx is /dashboard/users.
Use route groups when you need:
- A shared layout for a subset of pages without that segment appearing in the URL
- Logical separation of pages that belong to different parts of the app
Server vs. Client Separation
The project enforces a hard boundary between server and client code.
Server Components (no directive, the default in App Router):
- Fetch data directly using Prisma
- Access session via
getSession()fromlib/server/auth-helpers.ts - Never import client-only hooks (
useState,useEffect,useSession)
Client Components ("use client" at the top of the file):
- Handle interactivity, forms, and client-side state
- Access session via
useSession()fromlib/auth-client.ts - Never import
lib/db.tsor any server-only module
Server Actions ("use server" at the top of the file or function):
- Used for mutations only: form submissions, writes, deletes
- Data fetching belongs in Server Components, not Server Actions
- Live in
app/actions/and are imported by Client Components
A practical rule: if a file needs onClick, useState, or a browser API, it is a Client Component. If it fetches or writes data, keep as much of that logic server-side as possible.
lib/validations/ Pattern
Every form and Server Action has a corresponding Zod schema in lib/validations/. The schema is the single source of truth for both client-side validation (via React Hook Form) and server-side validation in the Server Action.
// lib/validations/settings.ts
import { z } from "zod";
export const settingsSchema = z.object({
notifications: z.object({
productUpdates: z.boolean(),
marketingEmails: z.boolean(),
}),
});
export type SettingsFormValues = z.infer<typeof settingsSchema>;// Client Component — uses schema for form validation
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { settingsSchema, type SettingsFormValues } from "@/lib/validations/settings";
const form = useForm<SettingsFormValues>({
resolver: zodResolver(settingsSchema),
});// Server Action — uses the same schema to validate incoming data
"use server";
import { settingsSchema } from "@/lib/validations/settings";
export async function updateUserSettings(data: unknown) {
const parsed = settingsSchema.parse(data);
// ...
}This pattern prevents validation logic from diverging between the client and server over time.
lib/server/: Server-Only Code
The lib/server/ directory contains modules that must never be imported in Client Components or browser-bundled code.
lib/server/auth-helpers.ts exports getSession(), a cached wrapper around the Better Auth session check. Import this in Server Components and Server Actions to read the current user's session. It caches the result per request, so calling it multiple times in a single render is safe.
lib/server/s3.ts contains the Cloudflare R2 upload helpers. These use AWS SDK v3 and environment variables that are not prefixed with NEXT_PUBLIC_, so they are only available server-side.
Co-located Components
Some components sit next to the page that uses them rather than in the top-level components/ directory. For example, app/(site)/bento.tsx is used only by the homepage, so there is no reason to share it. Co-location keeps the components/ directory focused on genuinely shared code.
Move a component to components/ only when it is used in more than one place.
Important Files
| File | Purpose |
|---|---|
lib/config.ts | App name, URLs, social handles, OG image, metadata generator |
lib/pricing.ts | Plan names, prices, features: the only place these are defined |
lib/auth.ts | Better Auth server configuration including Stripe plugin |
lib/auth-client.ts | Better Auth client hooks (useSession, subscription methods) |
lib/db.ts | Prisma client singleton: import this everywhere you need database access |
lib/email.ts | Email sending with SMTP2Go: HTML templates are defined here |
lib/validations/env.ts | All environment variables with types and validation, checked at startup |
prisma/schema.prisma | Database models: User, Session, Account, Verification, Subscription |
app/api/auth/[...all]/route.ts | Better Auth API handler: handles all auth and Stripe webhook requests |
app/api/health/route.ts | Returns 200 OK, used by deployment platforms for health checks |
components/subscription-provider.tsx | Wraps the dashboard to make subscription state available via context |