Project Structure
Understanding ProductReady's architecture - where everything lives and why
Project Structure
ProductReady follows a clear, logical structure. This guide helps you understand where everything lives and where to add your code.
New to Next.js? This structure follows Next.js 15+ App Router conventions with additional organization for scalability.
Overview
apps/productready/
├── src/ # All application code
│ ├── app/ # Next.js pages & routes
│ ├── components/ # React components
│ ├── db/ # Database schema & queries
│ ├── lib/ # Utilities & configuration
│ └── server/ # Server-side code (tRPC)
├── content/ # Documentation (MDX)
├── public/ # Static assets
├── spec/ # Product specs & design docs
└── tests/ # Test filesDetailed Structure
apps/productready
├─ src
│ ├─ app
│ │ ├─ (home)/
│ │ ├─ dashboard/
│ │ ├─ api/health/route.ts
│ │ ├─ docs/[[...slug]]/page.tsx
│ │ ├─ layout.tsx
│ │ └─ global.css
│ ├─ components
│ │ ├─ ui/ (button.tsx, card.tsx, ...)
│ │ └─ auth/auth-button.tsx
│ ├─ db
│ │ ├─ schema/ (users.ts, tasks.ts, posts.ts)
│ │ ├─ index.ts
│ │ └─ seed.ts
│ ├─ lib
│ │ ├─ trpc/client.ts
│ │ ├─ auth.ts
│ │ └─ demo.ts
│ └─ server
│ ├─ routers/ (tasks.ts, users.ts)
│ ├─ trpc.ts
│ └─ index.ts
├─ content
│ └─ docs
│ ├─ en/ (index.mdx, quick-start.mdx, ...)
│ └─ zh-CN/ (index.mdx, ...)
├─ public/assets/
├─ spec/ (product-design.md, icp-guide.md, ...)
├─ .env.example
├─ package.json
├─ next.config.mjs
└─ drizzle.config.tsKey Directories Explained
src/app/ - Next.js Pages
This is where your pages and routes live. Next.js uses file-based routing.
Folder structure = URL structure:
app/(home)/page.tsx→/(landing page)app/dashboard/page.tsx→/dashboardapp/dashboard/tasks/page.tsx→/dashboard/tasksapp/api/health/route.ts→/api/health(API endpoint)
Route Groups: (home) - Parentheses don't affect URL, just for organization
Special files:
page.tsx- The actual page componentlayout.tsx- Shared layout wrapperloading.tsx- Loading stateerror.tsx- Error boundaryroute.ts- API endpoint
Where to add:
- New pages → Create folder with
page.tsx - API endpoints → Create
route.tsinapp/api/
SPA Routing with Wouter
ProductReady uses Wouter for client-side SPA (Single Page Application) routing within dashboard pages.
Architecture Pattern:
- Next.js App Router handles top-level routes with SSR (
/dashboard,/agent,/kadmin) - Wouter handles sub-routes within each page for SPA navigation without full page reloads
Example (app/dashboard/page.tsx):
import { Route, Switch } from 'wouter';
import { SidebarProvider, SidebarInset } from '@/components/ui/sidebar';
import { AppSidebar } from '@/components/app-sidebar';
export default function DashboardPage() {
return (
<SidebarProvider>
<AppSidebar />
<SidebarInset>
{/* Wouter routes for SPA navigation */}
<Switch>
<Route path="/analytics">
<AnalyticsView />
</Route>
<Route path="/reports">
<ReportsView />
</Route>
<Route path="/">
<DashboardOverview />
</Route>
</Switch>
</SidebarInset>
</SidebarProvider>
);
}Benefits:
- ✅ Fast navigation - No full page reloads between sub-routes
- ✅ Better UX - Smooth transitions, preserved sidebar state
- ✅ SEO-friendly - Next.js handles initial SSR, Wouter for client-side
- ✅ Independent sections - Each dashboard page has its own wouter instance
When to use:
- ✅ Dashboard sections with multiple views (
/dashboard,/agent,/kadmin) - ✅ Any page with tabs/navigation that doesn't need SSR per sub-route
- ❌ Public pages that need per-route SEO (use Next.js App Router instead)
src/components/ - React Components
Reusable UI components organized by category.
Organization:
components/
├── ui/ # Generic UI components (Button, Card, Dialog)
├── auth/ # Auth-specific (LoginForm, AuthButton)
├── dashboard/ # Dashboard-specific components
└── shared/ # Truly shared across all pagesWhen to create a component:
- ✅ Used in 2+ places
- ✅ Complex logic that clutters page
- ✅ Reusable pattern (forms, cards, modals)
When NOT to:
- ❌ One-off UI (just put in page.tsx)
- ❌ Too granular (don't extract every
<div>)
KUI Library: Don't modify packages/kui directly! Create wrapper components in src/components/ui/ instead.
src/db/ - Database Layer
Everything database-related: schemas, migrations, queries.
Key files:
schema/*.ts- Table definitions (Drizzle ORM)index.ts- Database client exportseed.ts- Sample data for development
Example schema (schema/tasks.ts):
import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
export const tasks = pgTable('tasks', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
description: text('description'),
status: text('status', {
enum: ['pending', 'in_progress', 'completed']
}).default('pending'),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
});Where to add:
- New table → Create
schema/your-table.ts - Queries → Add to tRPC router (see
/server/routers/)
src/server/ - Server-Side Code
Backend logic: tRPC routers, procedures, middleware.
Structure:
server/
├── routers/ # tRPC routers (one per domain)
│ ├── tasks.ts # tasks.list, tasks.create, etc.
│ ├── users.ts # users.me, users.update, etc.
│ └── index.ts # Combines all routers
├── trpc.ts # tRPC config & procedures
└── index.ts # Main tRPC app exportExample router (routers/tasks.ts):
import { z } from 'zod';
import { createTRPCRouter, protectedProcedure } from '../trpc';
export const tasksRouter = createTRPCRouter({
list: protectedProcedure
.input(z.object({ limit: z.number().default(20) }))
.query(async ({ ctx, input }) => {
return ctx.db.select().from(tasks).limit(input.limit);
}),
create: protectedProcedure
.input(z.object({ title: z.string() }))
.mutation(async ({ ctx, input }) => {
return ctx.db.insert(tasks).values(input);
}),
});Where to add:
- New API routes → Create router in
routers/ - Shared logic → Add to
trpc.ts(middleware, context)
src/lib/ - Utilities & Configuration
Helpers, configs, and third-party integrations.
Common files:
auth.ts- Better Auth configurationtrpc/client.ts- tRPC client setupdemo.ts- Demo mode utilitiesutils.ts- Shared helper functions
What goes here:
- Configuration objects
- Helper functions
- Third-party SDK wrappers
- Constants and enums
content/docs/ - Documentation
MDX files for documentation (this page you're reading!).
Structure:
content/docs/
├── en/ # English docs
│ ├── index.mdx
│ ├── quick-start.mdx
│ └── features/
└── zh-CN/ # Chinese docs
└── index.mdxPowered by Fumadocs - automatically generates:
- Sidebar navigation
- Search functionality
- Table of contents
- Syntax highlighting
public/ - Static Assets
Files served directly at root URL.
What goes here:
public/logo.png→https://yoursite.com/logo.pngpublic/assets/icons/→ Static iconspublic/robots.txt→ SEO filespublic/favicon.ico→ Site favicon
What NOT to put:
- ❌ Large images (use CDN instead)
- ❌ User uploads (use S3/Cloudinary)
- ❌ Sensitive files
spec/ - Product Specifications
Internal docs for product, design, and engineering.
Files:
product-design.md- PRD (product requirements)icp-guide.md- Ideal customer profilesmarketing-guide.md- Messaging and positioningdesign-system.md- Design tokens and patterns
For: Internal team alignment, onboarding, decisions
Not for: User-facing documentation (that's in content/docs/)
Common Patterns
Adding a New Feature
Example: Add a "Comments" feature
-
Database schema →
src/db/schema/comments.tsexport const comments = pgTable('comments', { id: serial('id').primaryKey(), taskId: integer('task_id').references(() => tasks.id), content: text('content').notNull(), authorId: text('author_id').notNull(), createdAt: timestamp('created_at').defaultNow(), }); -
tRPC router →
src/server/routers/comments.tsexport const commentsRouter = createTRPCRouter({ list: protectedProcedure.query(...), create: protectedProcedure.mutation(...), }); -
UI component →
src/components/comments/comment-list.tsxexport function CommentList({ taskId }: { taskId: number }) { const { data } = trpc.comments.list.useQuery({ taskId }); // ... render comments } -
Page → Use component in
src/app/dashboard/tasks/[id]/page.tsx<CommentList taskId={task.id} /> -
Migrations → Run
pnpm db:generateandpnpm db:migrate
Adding a New Page
Example: Add /dashboard/analytics
-
Create folder:
src/app/dashboard/analytics/ -
Add page:
src/app/dashboard/analytics/page.tsxexport default function AnalyticsPage() { return ( <div> <h1>Analytics</h1> {/* Your content */} </div> ); } -
(Optional) Add layout:
layout.tsxif you need custom wrapper -
Visit:
http://localhost:3000/dashboard/analytics
Adding an API Endpoint
Example: Add /api/webhook
-
Create:
src/app/api/webhook/route.tsimport { NextResponse } from 'next/server'; export async function POST(request: Request) { const body = await request.json(); // Handle webhook return NextResponse.json({ success: true }); } -
Access:
POST https://yoursite.com/api/webhook
File Naming Conventions
Pages & Routes:
page.tsx- Page componentlayout.tsx- Layout wrapperroute.ts- API endpoint[id]- Dynamic route (e.g.,/tasks/[id])[[...slug]]- Catch-all route (docs)
Components:
kebab-case.tsx- Component files (user-profile.tsx)PascalCase- Component names (UserProfile)
Utilities:
camelCase.ts- Utility files (formatDate.ts)camelCase- Function names (formatDate())
Database:
snake_case- Table/column names (created_at)camelCase- TypeScript variables (createdAt)
Configuration Files
package.json
Dependencies, scripts, and metadata.
Key scripts:
{
"dev": "next dev --port 3000",
"build": "next build",
"db:migrate": "drizzle-kit migrate"
}next.config.mjs
Next.js configuration (rewrites, headers, env).
drizzle.config.ts
Database connection and migration settings.
tsconfig.json
TypeScript compiler options.
Path aliases:
{
"paths": {
"~/*": ["./src/*"]
}
}Lets you import like: import { db } from '~/db'
Best Practices
✅ Do
- Keep page components small (extract to
/components) - One router per domain (
tasksRouter,usersRouter) - Co-locate related files (feature folders)
- Use path aliases (
~/*instead of../../..) - Follow existing patterns in codebase
❌ Don't
- Put business logic in page components
- Create huge "god" routers with 20+ procedures
- Mix database queries with UI code
- Duplicate code (extract to utilities)
- Modify
packages/kuidirectly
Next Steps
Now that you understand the structure:
Questions? Check the Troubleshooting guide.