Project Structure
A tour of the folders in DirectoryLaunch — what lives where, what to edit, what to leave alone, and how AI editors navigate the codebase.
Why this matters
Most setup time on a new directory is finding things, not building them: which file controls the homepage hero, where do you change the price of premium listings, which folder owns the email templates. DirectoryLaunch's structure is intentionally flat and predictable — config/ for everything you tune, lib/ for engine code you mostly don't touch, app/ for pages, components/ for UI primitives.
The goal of this page is to be the map you reach for in the first week, the cheatsheet for AI editors (Claude Code, Cursor) so they place new files correctly, and the architecture overview that prevents the most common mistake: putting business logic in components instead of lib/. Skim it once, bookmark it, come back when you need it.
You don't need to understand every folder to ship. This page is a map — come back when you need to know where something lives.
Top-level folders
my-directory/
├── app/ ← Pages and API routes (Next.js App Router)
├── components/ ← Reusable React components
├── config/ ← ALL customization lives here
├── hooks/ ← React hooks (useUser, useFeatures, …)
├── lib/ ← Business logic (DB, auth, payments, email)
├── messages/ ← i18n translations (if enabled)
├── public/ ← Static files (images, fonts)
├── scripts/ ← Utility scripts (db:test, db:migrate, …)
├── supabase/ ← schema.sql — the full database structure
├── types/ ← TypeScript types
├── .env.local ← Your secret keys (never commit this!)
├── CLAUDE.md ← Architecture notes (safe to read)
└── package.json ← Dependencies and scripts
The folders you'll actually edit
config/ — your control panel
This is where you'll spend 90% of your setup time. Each file controls one area:
| File | What you edit here |
|---|---|
site.config.ts | Brand name, tagline, domain, contact emails, social links |
features.config.ts | On/off switches for partners, ratings, AI, i18n, etc. |
plans.config.ts | Your pricing tiers (free, premium, custom) |
themes.config.ts | 14 built-in color themes |
payments.config.ts | Which payment provider to use (Stripe by default) |
email.config.ts | Sender name, reply-to, signature |
analytics.config.ts | GA / PostHog keys (reads from env vars) |
ai.config.ts | AI provider and model for description generation |
i18n.config.ts | Languages the site supports |
directory.config.ts | Page size, sort options, seed categories |
advertising.config.ts | Promotion / sponsor pricing |
marketing.config.ts | The banner at the top of every page |
See the Configuration section for a deep dive on each.
app/ — pages and APIs
Next.js App Router — each folder is a URL path. The project uses route groups (folders in parentheses) to share layouts:
| Route group | Who sees it | Examples |
|---|---|---|
(marketing)/ | Everyone | /, /pricing, /blog, /project/[slug] |
(dashboard)/ | Logged-in users | /dashboard, /submit, /profile, /settings |
(admin)/admin/ | Admins only | /admin, /admin/projects, /admin/categories |
auth/ | Everyone | /auth/signin, /auth/callback |
api/ | Server routes | /api/payments, /api/webhooks/stripe, /api/cron/* |
components/ — UI building blocks
| Subfolder | Contents |
|---|---|
ui/ | shadcn/ui primitives (Button, Card, Dialog, …) — don't edit these by hand |
layout/ | Header, Footer |
directory/ | Project cards, category badges |
forms/ | Image upload, newsletter signup |
admin/ | Admin-only UI (CSV import, promotion dialogs, …) |
marketing/ | Promo banners, partner sections |
shared/ | Providers, error boundary, language switcher |
The folders you'll probably not touch
lib/ — the engine
Business logic. You rarely edit this, but it's where to look if something breaks.
supabase/— database client, auth helpers, MongoDB-style query wrapperpayments/— Stripe integrationvalidations/— Zod schemas (the single source of truth for types)email.ts,notifications.ts,webhooks.ts,rate-limit.ts,seo.ts, …
supabase/schema.sql
The entire database — tables, indexes, permissions, triggers — in a single idempotent SQL file. You run it once when you set up Supabase. See Supabase Setup.
types/
Auto-re-exports types inferred from Zod schemas. You'll import from here:
import type { User, App, Category } from '@/types';The @/ import shortcut
Anywhere in the code, @/ means "the project root." So:
import { siteConfig } from '@/config/site.config';
import { db } from '@/lib/supabase/database';
import { Button } from '@/components/ui/button';
import type { User } from '@/types';Files you should never commit
.env.local— contains secrets. Already in.gitignore.node_modules/— installed dependencies. Already ignored..next/— build output. Already ignored.
A real-world walkthrough: where each common task lives
You're going to make ten common changes in your first month. Here's where each one lives:
- "Change the homepage hero copy" →
app/(marketing)/page.tsx— direct edit, no config indirection. - "Add a new pricing tier" →
config/plans.config.ts. The pricing page (app/(marketing)/pricing/page.tsx) reads from there. - "Change the colour of category badges" →
config/themes.config.ts— pick a different theme or add a custom one. - "Add a new field to the submission form" → Three coordinated changes: (1) Zod schema in
lib/validations/schemas.ts, (2) form UI incomponents/forms/, (3) DB column insupabase/schema.sql. - "Customise the welcome email" →
lib/email/templates/welcome.tsx(React Email components). - "Add a new API route" →
app/api/<name>/route.ts. Use the route skeleton from API Reference Overview. - "Change rate limit numbers" →
config/directory.config.ts → rateLimits. Restart server. - "Add a new admin page" →
app/(admin)/admin/<name>/page.tsx. Inherits the admin layout automatically. - "Change the OG image" → Replace
public/assets/og-image.png(1200×630). Or for dynamic per-page OG, seeapp/.../opengraph-image.tsxfiles. - "Add tracking for a new event" → Call
trackEvent('event_name', payload)fromlib/analytics.tswherever the event happens. Surface in/admin/analyticsif it's a top-level metric.
If a task doesn't fit one of these patterns, ask: "What kind of change is this?" Pricing → config. Page UI → app. Reusable UI → components. Server logic → lib. Database → supabase/schema.sql. The whole project obeys this hierarchy.
Common pitfalls
- Putting business logic inside components. Components should render;
lib/should compute. A component that hits Stripe directly is hard to test, hard to reuse, and tied to a specific render path. Move the call tolib/payments/and import the function. - Editing files in
components/ui/. These are shadcn/ui primitives. Modifying them breaks future updates. If you need a variant, copy the primitive intocomponents/ui/custom-button.tsxand edit that. - Adding new config values directly to
app/instead ofconfig/. Tomorrow you'll want to change the value, and you'll grep throughapp/looking for a magic number. Always create a config field first. - Forgetting to update
types/after schema changes. Zod is the single source of truth — types flow fromlib/validations/schemas.ts. If you add a column without updating the schema, your TypeScript will silently allowundefinedwhere the DB now requires a value. - Storing secrets in
config/files. Config is committed; secrets aren't. Always read fromprocess.env.*and provide a non-secret default. Anything matching_KEY,_SECRET,_TOKEN→ env, not config.
FAQ
Can I rename app/(marketing)/ to something else?
Technically yes — route groups in parentheses don't affect URLs. But the layout file inside that group is what gives marketing pages the public header and footer. If you rename, update layout imports too. The naming convention is also called out in CLAUDE.md so AI editors place new pages in the right group.
Where do I add a CRON job?
app/api/cron/<name>/route.ts. Protect it with a check against the CRON_SECRET env var. Then add the schedule to vercel.json (or vercel.ts) under crons. Vercel hits the route at the configured schedule.
Why is database access through lib/supabase/database.ts instead of direct Supabase calls?
The db wrapper provides a MongoDB-style API (findOne, insertMany, updateOne) that's typed against your Zod schemas. Calling Supabase directly in a route works, but you lose type safety on the response shape and you re-implement the same try/catch wrapping every time. Use db unless you need a feature it doesn't expose (like .rpc() calls).
Does CLAUDE.md get loaded automatically by Claude Code?
Yes — Claude Code reads CLAUDE.md at the project root automatically. The file documents the architecture conventions so AI edits respect them: where to put new files, naming patterns, the Zod-first type approach. Keep it short and accurate; it's the single biggest lever for AI-edit quality.
What's next
Ready to configure Supabase and go live? Follow the Quick Start tutorial.