Project Structure
Understand how Starter Free is organized so you can extend it without breaking its architecture.
OverviewLink to section
Starter Free is structured for clarity and long-term evolution.
This is not a demo folder. It is a production-shaped architecture designed to help you extend features without turning pages into large, fragile files.
Understanding the structure early helps you:
- extend features cleanly
- keep routes readable
- wire backend progressively
- avoid refactors caused by mixed responsibilities
Key idea
You are not only learning where files live. You are learning how product architecture scales from a mocked SaaS surface to a real product.
Mental modelLink to section
Pages define routes. Components define features. UI defines primitives.
| Layer | Responsibility |
|---|---|
app/ | Routing, layouts, and product entry points |
components/ | Feature composition and interaction logic |
@pycolors/ui | Stateless UI primitives used across the product |
System rule
Keep pages thin. When interaction logic grows, move it into a feature folder.
StructureLink to section
This structure is intentionally small. It separates route ownership from feature composition.
Architecture flowLink to section
App Router pages
↓
Feature components
↓
PyColors UI primitivesA page should describe the product surface. Feature components should own interactions. UI primitives should stay reusable and stateless.
Route layer
The route layer defines URLs, page structure, layout composition, and high-level orchestration.
App directoryLink to section
The app/ directory is the routing layer.
It defines:
- auth entry points
- dashboard pages
- product routes
- settings and billing surfaces
- high-level layout composition
Page rule
A page should compose the feature. It should not become the feature itself.
Components directoryLink to section
The components/ directory contains feature-level building blocks.
For example, the projects feature keeps table rendering, row actions, dialogs, and mock types together.
This keeps the route clean:
import { ProjectTable } from "@/components/projects/project-table";
export default function ProjectsPage() {
return <ProjectTable />;
}And moves feature behavior into the feature folder.
export function ProjectRowActions() {
return (
// dropdown actions, rename dialog, delete confirmation
// and later API wiring live here
null
);
}Mock data strategyLink to section
Mock data lives close to the feature using it.
export type Project = {
id: string;
name: string;
status: "active" | "draft" | "archived";
};
export const MOCK_PROJECTS: Project[] = [
{
id: "project_1",
name: "Acme Dashboard",
status: "active",
},
];Later, you replace the mock source with:
- API calls
- server actions
- database queries
- authenticated user data
The UI structure can remain stable.
Why this is powerful
Mock data is temporary. Product structure is not. Keep the structure stable and replace the source of truth when the product is ready.
PageShell patternLink to section
Main SaaS pages use a shared shell for consistent page structure.
<PageShell
title="Projects"
description="Manage your product entities."
actions={...}
meta={...}
/>Use this pattern to keep:
- page spacing consistent
- headers predictable
- actions aligned
- metadata structured
Do not bypass it unless the page truly needs a different layout.
Dialog patternLink to section
Dialogs are isolated from the page and controlled by their parent feature.
Recommended ownership:
| Concern | Owner |
|---|---|
| Open state | Parent feature component |
| Form UI | Dialog component |
| API call later | Feature/service layer |
| Visual primitive | @pycolors/ui Dialog |
This makes rename and delete flows easier to wire later without changing page structure.
Add a new featureLink to section
Use the existing Projects pattern as your template.
Add the routeLink to section
mkdir -p app/reports
touch app/reports/page.tsxCreate the feature folderLink to section
mkdir -p components/reports
touch components/reports/report-table.tsx
touch components/reports/report-row-actions.tsx
touch components/reports/report-types.tsCompose the pageLink to section
import { ReportTable } from "@/components/reports/report-table";
export default function ReportsPage() {
return <ReportTable />;
}Decision guideLink to section
Use the Starter Free structure if:
- you want thin route files
- features need to evolve independently
- mock data will later become backend data
- dialogs and row actions need to stay reusable
- you want a clear path from frontend surface to production wiring
Avoid changing the structure if:
- you have not explored the full vertical slice yet
- the change only saves a few lines today
- the route would start owning too much logic
- UI primitives would start fetching data
- the mock layer would become scattered globally
Prefer
- feature-based folders
- thin pages
- mock data close to the feature
- isolated dialogs and row actions
- UI primitives that stay stateless
Avoid
- putting feature logic directly in pages
- fetching data inside UI primitives
- placing all mock data in one global file
- duplicating dialog logic across routes
- deleting structure before understanding it
Backend wiring laterLink to section
When you connect a backend, the structure stays familiar.
| Today | Later |
|---|---|
MOCK_PROJECTS | database-backed projects |
| local dialog state | server action or API mutation |
| disabled billing portal | Stripe billing portal |
| mocked auth screens | real auth provider |
| static members | organization members from backend |
The goal is not to rewrite the UI. The goal is to replace the data source.
Upgrade when wiring becomes the blocker
Starter Free validates the product surface. Starter Pro wires the business layer with production-shaped auth, billing, backend, and delivery foundations. → /docs/starter-pro