skills/project-structure/SKILL.md
Guides React/Next.js/TypeScript project organization using feature-based architecture. Use when structuring new projects, reorganizing codebases, or deciding where to place new code.
npx skillsauth add Chris-Maskey/opencode-config project-structureInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Organize code by feature/domain, not by file type. Enforce unidirectional code flow: shared → features → app.
This approach scales well for medium-to-large React, Next.js, and TypeScript projects while keeping features independent and maintainable.
src/
├── app/ # Application layer (routing, providers)
│ ├── routes/ # Route definitions / pages
│ ├── app.tsx # Main application component
│ ├── provider.tsx # Global providers wrapper
│ └── router.tsx # Router configuration
├── assets/ # Static files (images, fonts)
├── components/ # Shared UI components
├── config/ # Global configuration, env variables
├── features/ # Feature-based modules (main code lives here)
├── hooks/ # Shared hooks
├── lib/ # Pre-configured libraries (axios, dayjs, etc.)
├── stores/ # Global state stores
├── testing/ # Test utilities and mocks
├── types/ # Shared TypeScript types
└── utils/ # Shared utility functions
Each feature is a self-contained module:
src/features/users/
├── api/ # API requests & React Query hooks
├── components/ # Feature-scoped UI components
├── hooks/ # Feature-scoped hooks
├── stores/ # Feature state (Zustand, etc.)
├── types/ # Feature-specific types
└── utils/ # Feature utility functions
Only include folders you need. Don't create empty folders "just in case."
┌─────────┐ ┌──────────┐ ┌─────┐
│ shared │ ──► │ features │ ──► │ app │
└─────────┘ └──────────┘ └─────┘
| From | Can Import From |
|------|-----------------|
| app | features, shared (components, hooks, lib, types, utils) |
| features | shared only |
| shared | Other shared modules only |
Features cannot import from each other. Compose features at the app level.
| Code Type | Location | Example |
|-----------|----------|---------|
| Route/page component | app/routes/ | app/routes/users/page.tsx |
| Feature-specific component | features/[name]/components/ | features/users/components/UserCard.tsx |
| Reusable UI component | components/ | components/Button.tsx |
| Feature API calls | features/[name]/api/ | features/users/api/getUsers.ts |
| Shared utility | utils/ | utils/formatDate.ts |
| Feature utility | features/[name]/utils/ | features/users/utils/validateUser.ts |
| Global state | stores/ | stores/authStore.ts |
| Feature state | features/[name]/stores/ | features/users/stores/userFilterStore.ts |
| Library wrapper | lib/ | lib/axios.ts, lib/dayjs.ts |
| Global types | types/ | types/api.ts |
| Feature types | features/[name]/types/ | features/users/types/user.ts |
// .eslintrc.js
module.exports = {
rules: {
'import/no-restricted-paths': [
'error',
{
zones: [
// Disables cross-feature imports
{
target: './src/features/users',
from: './src/features',
except: ['./users'],
},
{
target: './src/features/posts',
from: './src/features',
except: ['./posts'],
},
// Add more features as needed
],
},
],
},
};
// .eslintrc.js
module.exports = {
rules: {
'import/no-restricted-paths': [
'error',
{
zones: [
// Features cannot import from app
{
target: './src/features',
from: './src/app',
},
// Shared cannot import from features or app
{
target: [
'./src/components',
'./src/hooks',
'./src/lib',
'./src/types',
'./src/utils',
],
from: ['./src/features', './src/app'],
},
],
},
],
},
};
// src/features/users/api/getUsers.ts
import { useQuery } from '@tanstack/react-query';
import { api } from '@/lib/axios';
import type { User } from '../types/user';
export const getUsers = async (): Promise<User[]> => {
const response = await api.get('/users');
return response.data;
};
export const useUsers = () => {
return useQuery({
queryKey: ['users'],
queryFn: getUsers,
});
};
// src/features/users/components/UserList.tsx
import { useUsers } from '../api/getUsers';
import { UserCard } from './UserCard';
export function UserList() {
const { data: users, isLoading } = useUsers();
if (isLoading) return <Spinner />;
return (
<div className="grid gap-4">
{users?.map((user) => (
<UserCard key={user.id} user={user} />
))}
</div>
);
}
// src/app/routes/dashboard/page.tsx
import { UserList } from '@/features/users/components/UserList';
import { PostList } from '@/features/posts/components/PostList';
import { ActivityFeed } from '@/features/activity/components/ActivityFeed';
export function DashboardPage() {
return (
<div className="grid grid-cols-3 gap-6">
<UserList />
<PostList />
<ActivityFeed />
</div>
);
}
// src/features/users/index.ts
export * from './components/UserList'; // Breaks tree-shaking in Vite
export * from './api/getUsers';
Instead, import directly:
import { UserList } from '@/features/users/components/UserList';
import { useUsers } from '@/features/users/api/getUsers';
// src/features/posts/components/PostCard.tsx
import { UserAvatar } from '@/features/users/components/UserAvatar'; // Bad
Instead, lift shared components:
// Move UserAvatar to src/components/UserAvatar.tsx
import { UserAvatar } from '@/components/UserAvatar'; // Good
If a component is only used by one feature, keep it in that feature:
// Bad: Polluting shared components
src/components/
├── UserCard.tsx # Only used by users feature
├── PostCard.tsx # Only used by posts feature
└── Button.tsx # Actually shared
// Good: Feature-scoped components
src/features/users/components/UserCard.tsx
src/features/posts/components/PostCard.tsx
src/components/Button.tsx
For Next.js with App Router, adapt the structure:
src/
├── app/ # Next.js App Router (routes)
│ ├── (auth)/ # Route group
│ │ ├── login/
│ │ └── register/
│ ├── dashboard/
│ ├── layout.tsx
│ └── page.tsx
├── components/ # Shared components
├── features/ # Feature modules (non-routing code)
│ ├── auth/
│ ├── dashboard/
│ └── users/
├── lib/
└── ...
Keep route handlers minimal - delegate to feature modules:
// src/app/users/page.tsx
import { UserList } from '@/features/users/components/UserList';
export default function UsersPage() {
return <UserList />;
}
@/ for src/)src/features/[name]/ directorytools
Anti-patterns and mistakes to avoid as a product manager. Use when evaluating leadership behaviors, improving team dynamics, reflecting on management practices, or onboarding new product managers.
development
Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".
testing
Design effective CTAs using visual attention and gaze psychology principles. Use when designing landing pages, button hierarchies, conversion elements, or optimizing user attention flow through interfaces.
tools
Run agent-browser + Chrome inside Vercel Sandbox microVMs for browser automation from any Vercel-deployed app. Use when the user needs browser automation in a Vercel app (Next.js, SvelteKit, Nuxt, Remix, Astro, etc.), wants to run headless Chrome without binary size limits, needs persistent browser sessions across commands, or wants ephemeral isolated browser environments. Triggers include "Vercel Sandbox browser", "microVM Chrome", "agent-browser in sandbox", "browser automation on Vercel", or any task requiring Chrome in a Vercel Sandbox.