Next Starter Logo

Project Structure

A map of the Next Starter directory layout: every folder and file in this Next.js SaaS kit, plus conventions for where new pages, components, and actions go.

Read this before adding pages or features. The tree below is annotated. The conventions section explains the patterns and shows where new code goes.

Directory Tree

app/
├── layout.tsx               # Root layout: HTML shell, fonts, ThemeProvider, Toaster
├── globals.css              # Global stylesheet (Tailwind v4, theme tokens)
├── error.tsx                # Root error boundary
├── not-found.tsx            # 404 page
├── manifest.ts              # Web app manifest
├── robots.ts                # robots.txt generation
├── sitemap.ts               # Sitemap generation

├── (site)/                  # Marketing pages (public — route group, no URL segment)
│   ├── layout.tsx           # Marketing layout (header, footer)
│   ├── page.tsx             # Homepage
│   ├── hero.tsx             # Co-located homepage sections ↓
│   ├── logo-cloud.tsx
│   ├── feature-left.tsx
│   ├── feature-right.tsx
│   ├── bento.tsx
│   ├── faq.tsx
│   ├── faq-data.ts
│   ├── call-to-action.tsx
│   ├── pricing/
│   │   ├── page.tsx         # Standalone pricing page
│   │   └── quote.tsx        # (co-located)
│   ├── contact/
│   │   ├── page.tsx
│   │   └── contact-form.tsx # (co-located)
│   ├── about/page.tsx
│   ├── privacy/page.tsx
│   └── terms/page.tsx

├── auth/                    # Authentication flows (each page co-locates its form)
│   ├── layout.tsx           # Split-screen auth layout
│   ├── sign-in/             #   page.tsx + sign-in-form.tsx
│   ├── register/            #   page.tsx + register-form.tsx
│   ├── forgot-password/     #   page.tsx + forgot-password-form.tsx
│   ├── reset-password/      #   page.tsx + reset-password-form.tsx
│   ├── verify-email/        #   page.tsx + verify-email-form.tsx (6-digit OTP)
│   └── error/               #   page.tsx + error-content.tsx

├── onboarding/              # First-run wizard (gates dashboard until complete)
│   ├── layout.tsx
│   ├── page.tsx
│   ├── onboarding-wizard.tsx
│   ├── profile-step.tsx
│   ├── plan-step.tsx
│   └── complete/page.tsx

├── dashboard/               # Protected user area (session required)
│   ├── layout.tsx           # Sidebar, breadcrumb, mobile header
│   ├── page.tsx             # + dashboard-content.tsx, loading.tsx
│   ├── billing/             # Stripe subscription management
│   │   ├── page.tsx         # + billing-content.tsx, current-plan-card.tsx, loading.tsx
│   ├── settings/            # page.tsx + settings-form.tsx + loading.tsx
│   ├── profile/             # page.tsx + profile-form.tsx + loading.tsx
│   │   ├── change-email/    #   page.tsx + change-email-form.tsx + loading.tsx
│   │   └── change-password/ #   page.tsx + change-password-form.tsx + loading.tsx
│   └── (admin)/             # Admin-only routes (route group — no URL segment)
│       ├── layout.tsx       # Enforces admin role check
│       ├── users/           # page.tsx + loading.tsx  → /dashboard/users
│       └── files/           # page.tsx + files-content.tsx + loading.tsx

├── actions/                 # Server Actions (mutations only)
│   ├── contact.ts           # Contact form submission
│   ├── files.ts             # File upload/delete
│   ├── onboarding.ts        # Onboarding wizard completion
│   ├── turnstile.ts         # Cloudflare Turnstile verification
│   ├── settings.ts          # Account settings update
│   └── user.ts              # User management (admin)

└── api/
    ├── auth/[...all]/route.ts  # Better Auth + Stripe webhook handler
    └── health/route.ts        # Health check for deployments

components/
├── ui/                      # shadcn/ui primitives
├── pricing/pricing-table.tsx
├── dashboard/               # sidebar, breadcrumb, mobile-header,
│                            #   past-due-banner, bronze-card, gold-card
├── users/                   # columns, users-table, user-avatar, dialogs/
├── auth/google-signin-button.tsx
├── captcha-widget.tsx       # Turnstile widget + useTurnstile hook (contact form)
├── animate-on-scroll.tsx    # Scroll-triggered animation wrapper
├── hero.tsx                 # Reusable hero section
├── header.tsx
├── footer.tsx
├── mobile-nav.tsx
├── theme-toggle.tsx
├── theme-provider.tsx
└── 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
├── logger.ts                # Pino structured logger
├── utils.ts                 # cn(), formatDateLong(), isPathSafe()
├── file-utils.ts            # File operation helpers
├── client/                  # Client-only utilities (avatar.ts, image.ts)
├── email/                   # SMTP2Go + React Email templates
│   ├── index.tsx            # sendEmail + async template helpers
│   ├── layout.tsx           # Shared EmailLayout component
│   ├── *.tsx                # One template per email (verification, password-reset,
│   │                        #   subscription-started, payment-failed, …)
│   └── blocked-domains.json # Disposable-email blocklist
├── validations/             # Zod schemas (auth, contact, env, files, settings, user)
└── server/                  # Server-only code
    ├── auth-helpers.ts      # getSession() — cached per request
    └── s3.ts                # S3-compatible storage helpers (R2, S3, …)

hooks/use-mobile.ts          # Mobile viewport detection hook
types/api.ts                 # Shared API type definitions

prisma/
├── schema.prisma            # Database schema
└── migrations/              # Applied migrations

generated/prisma/            # Prisma client output (generated — do not edit)

public/                      # Static assets
├── og-image.png, screenshot.png
├── logo.svg, logo-dark.svg, icon.svg
├── icon-192.png, icon-512.png, icon-mask.png, apple-touch-icon.png
└── images/                  # bento, features, logos, team, testimonials

Conventions

Route groups. A folder name in parentheses is a route group. It shares a layout but adds no URL segment. (site) wraps marketing pages in the public header and footer, so app/(site)/pricing/page.tsx serves /pricing. (admin) sits inside dashboard/, so it keeps the dashboard's session check and adds a role check on top. app/dashboard/(admin)/users/page.tsx serves /dashboard/users.

Co-location. A component used by one page lives next to that page (for example app/(site)/bento.tsx and every *-form.tsx). Move it to components/ only when a second page needs it. This keeps components/ for code that is truly shared.

Server vs. client. Server Components are the default. They fetch data with Prisma and read the session with getSession() from lib/server/auth-helpers.ts. Client Components (files marked "use client") handle interactivity. They read the session with useSession() from lib/auth-client.ts. A Client Component must never import lib/db.ts or anything under lib/server/. Put writes in Server Actions under app/actions/. Keep data fetching in Server Components.

Validation. Each form and action has a Zod schema in lib/validations/. (Zod is a schema library that checks data shapes.) The same schema runs on the client through zodResolver in React Hook Form, and on the server inside the Server Action. One schema for both sides means they never drift apart.

Where to add things

You want to add…Put it here
A marketing pageapp/(site)/<route>/page.tsx
A protected pageapp/dashboard/<route>/page.tsx
An admin-only pageapp/dashboard/(admin)/<route>/page.tsx
A mutation (form submit, write)a Server Action in app/actions/ + a Zod schema in lib/validations/
A shared componentcomponents/ (else co-locate next to its page)
An emaila template in lib/email/, wired through sendEmail in lib/email/index.tsx
A server-only helperlib/server/

Important Files

FilePurpose
lib/config.tsApp name, URLs, social handles, OG image, metadata generator
lib/pricing.tsPlan names, prices, features; the only place these are defined
lib/auth.tsBetter Auth server config including the Stripe plugin
lib/auth-client.tsBetter Auth client hooks (useSession, subscription methods)
lib/db.tsPrisma client singleton; import wherever you need DB access
lib/email/index.tsxEmail sending (SMTP2Go) + async React Email template helpers
lib/validations/env.tsAll env vars with types and validation, checked at startup
prisma/schema.prismaModels: User, Session, Account, Verification, Subscription
app/api/auth/[...all]/route.tsBetter Auth API handler for all auth and Stripe webhook requests
components/subscription-provider.tsxExposes subscription state to the dashboard via context

On this page