The web app uses a feature-based architecture to keep code organized and scalable as your application grows.
Why Feature-Based?
Co-location Keep all code for a feature together
Scalability Add features without reorganizing
Team Collaboration Multiple devs can work on different features
Code Splitting Natural boundaries for lazy loading
Feature Structure
Each feature is a self-contained module:
features/
└── [feature-name]/
├── [feature]-page.tsx # Main page component
├── [feature]-*.tsx # Feature-specific components
├── use-[feature].ts # Feature-specific hooks
├── [feature]-store.ts # Zustand store (if needed)
├── [feature]-types.ts # TypeScript types
└── [feature]-api.ts # API calls
Not all features need all files. Start simple and add complexity as needed.
Example: Home Feature
The starter includes a simple home feature:
features/
└── home/
└── home-page.tsx
Home Page Component
features/home/home-page.tsx
"use client" ;
import { Heading } from "@/components/ui/heading" ;
import Image from "next/image" ;
export const HomePage = () => {
return (
< div className = "mx-auto max-w-2xl h-screen flex flex-col justify-center gap-6" >
< Image src = "/copilot-cli.svg" alt = "Copilot CLI" width = { 60 } height = { 60 } />
< Heading variant = "h1" className = "" >
Ship faster. Same team.
</ Heading >
< p className = "text-lg text-muted-foreground text-balance" >
GitHub Copilot Agents and Skills that accelerate modern frontend development.
</ p >
</ div >
);
};
Connecting to Routes
Route files in app/ are thin wrappers:
import { HomePage } from "@/features/home/home-page" ;
export default function Home () {
return < HomePage /> ;
}
This separation keeps routing logic in app/ and feature logic in features/, making it easy to refactor routes without touching feature code.
Building a New Feature
Let’s build a dashboard feature from scratch.
Create the feature directory
mkdir -p features/dashboard
Add the main page component
features/dashboard/dashboard-page.tsx
"use client" ;
import { Heading } from "@/components/ui/heading" ;
import { DashboardStats } from "./dashboard-stats" ;
import { DashboardActivity } from "./dashboard-activity" ;
export function DashboardPage () {
return (
< div className = "container py-8" >
< Heading variant = "h1" > Dashboard </ Heading >
< div className = "mt-8 grid gap-6 md:grid-cols-3" >
< DashboardStats />
</ div >
< div className = "mt-8" >
< DashboardActivity />
</ div >
</ div >
);
}
Add feature-specific components
features/dashboard/dashboard-stats.tsx
import { Card , CardHeader , CardTitle , CardContent } from "@/components/ui/card" ;
export function DashboardStats () {
return (
<>
< Card >
< CardHeader >
< CardTitle > Total Users </ CardTitle >
</ CardHeader >
< CardContent >
< div className = "text-3xl font-bold" > 1,234 </ div >
</ CardContent >
</ Card >
{ /* More stat cards */ }
</>
);
}
features/dashboard/dashboard-activity.tsx
import { Card , CardHeader , CardTitle , CardContent } from "@/components/ui/card" ;
export function DashboardActivity () {
return (
< Card >
< CardHeader >
< CardTitle > Recent Activity </ CardTitle >
</ CardHeader >
< CardContent >
{ /* Activity list */ }
</ CardContent >
</ Card >
);
}
Create the route
import { DashboardPage } from "@/features/dashboard/dashboard-page" ;
export default function Dashboard () {
return < DashboardPage /> ;
}
Add metadata (optional)
import type { Metadata } from "next" ;
import { DashboardPage } from "@/features/dashboard/dashboard-page" ;
export const metadata : Metadata = {
title: "Dashboard" ,
description: "View your analytics and activity" ,
};
export default function Dashboard () {
return < DashboardPage /> ;
}
Feature with State Management
For features with complex state, add a Zustand store:
features/
└── todos/
├── todos-page.tsx
├── todos-list.tsx
├── todos-form.tsx
├── todos-store.ts # ← Zustand store
└── todos-types.ts
Example Store
features/todos/todos-store.ts
import { create } from 'zustand' ;
import type { Todo } from './todos-types' ;
interface TodosStore {
todos : Todo [];
addTodo : ( text : string ) => void ;
toggleTodo : ( id : string ) => void ;
deleteTodo : ( id : string ) => void ;
}
export const useTodosStore = create < TodosStore >(( set ) => ({
todos: [],
addTodo : ( text ) => set (( state ) => ({
todos: [ ... state . todos , { id: crypto . randomUUID (), text , completed: false }]
})),
toggleTodo : ( id ) => set (( state ) => ({
todos: state . todos . map ( todo =>
todo . id === id ? { ... todo , completed: ! todo . completed } : todo
)
})),
deleteTodo : ( id ) => set (( state ) => ({
todos: state . todos . filter ( todo => todo . id !== id )
})),
}));
Using the Store
features/todos/todos-page.tsx
"use client" ;
import { useTodosStore } from './todos-store' ;
import { TodosList } from './todos-list' ;
import { TodosForm } from './todos-form' ;
export function TodosPage () {
const todos = useTodosStore ( state => state . todos );
return (
< div className = "container py-8" >
< TodosForm />
< TodosList todos = { todos } />
</ div >
);
}
Feature with API Integration
For features that fetch data, add an API utility file:
features/posts/posts-api.ts
const API_BASE = process . env . NEXT_PUBLIC_API_URL ;
export async function getPosts () {
const res = await fetch ( ` ${ API_BASE } /posts` );
if ( ! res . ok ) throw new Error ( 'Failed to fetch posts' );
return res . json ();
}
export async function getPost ( id : string ) {
const res = await fetch ( ` ${ API_BASE } /posts/ ${ id } ` );
if ( ! res . ok ) throw new Error ( 'Failed to fetch post' );
return res . json ();
}
export async function createPost ( data : CreatePostData ) {
const res = await fetch ( ` ${ API_BASE } /posts` , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ( data ),
});
if ( ! res . ok ) throw new Error ( 'Failed to create post' );
return res . json ();
}
Server Components (Recommended)
Fetch data in Server Components for better performance:
features/posts/posts-page.tsx
import { getPosts } from './posts-api' ;
import { PostsList } from './posts-list' ;
export async function PostsPage () {
const posts = await getPosts ();
return (
< div className = "container py-8" >
< h1 > Posts </ h1 >
< PostsList posts = { posts } />
</ div >
);
}
Client Components (When Needed)
For interactive features, use client components with hooks:
features/posts/posts-page.tsx
"use client" ;
import { useEffect , useState } from 'react' ;
import { getPosts } from './posts-api' ;
export function PostsPage () {
const [ posts , setPosts ] = useState ([]);
const [ loading , setLoading ] = useState ( true );
useEffect (() => {
getPosts ()
. then ( setPosts )
. finally (() => setLoading ( false ));
}, []);
if ( loading ) return < div > Loading... </ div > ;
return < div > { /* Render posts */ } </ div > ;
}
Feature Organization Tips
Naming
Exports
Dependencies
Size
Consistent prefixes help identify feature files:features/dashboard/
├── dashboard-page.tsx
├── dashboard-stats.tsx
├── dashboard-chart.tsx
├── dashboard-store.ts
└── dashboard-types.ts
Avoid generic names like index.tsx or component.tsx. Named exports are preferred for better refactoring:// ✅ Good
export function DashboardPage () { /* ... */ }
// ❌ Avoid
export default function Page () { /* ... */ }
Exception: Route files (app/*/page.tsx) must use default exports. Feature isolation : Features should not import from each other.// ✅ Good - import from shared components
import { Button } from "@/components/ui/button" ;
// ❌ Bad - cross-feature dependency
import { DashboardStats } from "@/features/dashboard/dashboard-stats" ;
If you need shared logic, move it to components/ or create a shared utility. Keep features focused : If a feature grows too large, consider splitting it.features/
├── dashboard/ # Main dashboard
├── dashboard-analytics/ # Analytics sub-feature
└── dashboard-reports/ # Reports sub-feature
Best Practices
Single Responsibility Each feature should represent one user-facing capability
Self-Contained Features should not depend on other features
Thin Routes Keep app/ routes minimal, delegate to features
Start Simple Add complexity (stores, types, API) as needed
Next Steps
State Management Learn Zustand patterns for complex state
Components Build reusable UI components