Agent Skill
2/7/2026

feature-architecture

Guides feature-based architecture in Next.js applications. Activates when creating new features, organizing domain code, setting up barrel exports, or structuring feature modules with components, schemas, types, queries, mocks, stores, and constants.

M
madlado87
0GitHub Stars
1Views
npx skills add madlado87/agentes

SKILL.md

Namefeature-architecture
DescriptionGuides feature-based architecture in Next.js applications. Activates when creating new features, organizing domain code, setting up barrel exports, or structuring feature modules with components, schemas, types, queries, mocks, stores, and constants.

name: feature-architecture description: "Guides feature-based architecture in Next.js applications. Activates when creating new features, organizing domain code, setting up barrel exports, or structuring feature modules with components, schemas, types, queries, mocks, stores, and constants."

Feature-Based Architecture

This project follows a strict feature-based architecture. All domain logic is encapsulated inside src/features/. The src/app/ directory contains only thin route files.

IMPORTANT: Read this entire skill before creating any files. Every decision about where code lives is governed by this architecture.

Feature Structure

Every feature is a self-contained module:

src/features/[feature-name]/
├── index.ts                    # Barrel export — single entry point
├── schemas/
│   └── [name].schema.ts       # Zod schemas (single source of truth)
├── types/
│   └── [name].ts              # Types inferred from schemas via z.infer
├── components/
│   ├── [name].tsx              # React components (Server or Client)
│   └── [name].test.tsx         # Co-located unit tests
├── queries/
│   └── use-[resource].ts       # Key factory + client hooks + server prefetch
├── hooks/
│   └── use-[name].ts           # Custom hooks (not stores)
├── stores/
│   └── [name]-store.ts         # Zustand stores (use[Name]Store)
├── mocks/
│   └── [name].mock.ts          # Mock factories (createMock[Entity])
└── constants/
    └── [name].ts               # UPPER_SNAKE_CASE constants

Decision Tree: Where Does This Code Go?

Use this to decide where any new code belongs:

Is it domain-specific logic?
├── YES → src/features/[feature-name]/
│   ├── Is it a React component? → components/[name].tsx
│   ├── Is it a data shape/validation? → schemas/[name].schema.ts
│   ├── Is it a type inferred from a schema? → types/[name].ts
│   ├── Is it API data fetching (queries/mutations)? → queries/use-[resource].ts
│   ├── Is it a custom hook (NOT a store)? → hooks/use-[name].ts
│   ├── Is it client-only UI state? → stores/[name]-store.ts (Zustand)
│   ├── Is it test mock data? → mocks/[name].mock.ts
│   ├── Is it a filter definition? → schemas/[name]-filters.schema.ts (server + client filter schemas)
│   └── Is it a constant value? → constants/[name].ts
├── NO → Is it a UI primitive (button, input, dialog)?
│   ├── YES → src/components/ui/ (shadcn/ui — do NOT modify)
│   └── NO → Is it a shared utility or infrastructure?
│       ├── YES → src/lib/ (utils, api-client, query-client, etc.)
│       └── NO → Is it an external third-party service?
│           ├── YES → src/services/[name]/ (e.g., datadog, launchdarkly)
│           └── NO → Is it a cross-cutting provider?
│           ├── YES → src/lib/providers/[name]-provider.tsx
│           └── NO → Is it a global type?
│               ├── YES → src/types/
│               └── NO → Ask: does this really need to exist?

Barrel Export Rules

Every feature exports its public API through a single index.ts. External code NEVER imports from feature internals.

See examples/barrel-export.ts for the exact pattern.

Forbidden:

// ❌ NEVER import from feature internals
import { TaskCard } from "@/features/tasks/components/task-card";
import { taskSchema } from "@/features/tasks/schemas/task.schema";
import { useTasks } from "@/features/tasks/queries/use-tasks";

Correct:

// ✅ Always import from the barrel
import { TaskCard, taskSchema, useTasks } from "@/features/tasks";

What to Export in index.ts

Export everything that external code needs:

// src/features/tasks/index.ts

// Components
export { TaskCard } from "./components/task-card";
export { TaskList } from "./components/task-list";

// Queries (client hooks + server prefetch + key factory)
export { useTasks, useTask, prefetchTasks, prefetchTask, taskKeys } from "./queries/use-tasks";

// Schemas
export { taskSchema, createTaskInputSchema } from "./schemas/task.schema";

// Types (use `export type` for types)
export type { Task, CreateTaskInput } from "./types/task";

// Mocks (for external tests)
export { createMockTask, createMockTaskList } from "./mocks/task.mock";

// Stores
export { useTaskStore } from "./stores/task-store";

// Constants
export { TASK_STATUS } from "./constants/task-status";

Import Rules

FromCan import fromCannot import from
app/ pages@/features/[name] (barrel), @/components/ui/, @/lib/Feature internals
Feature A@/lib/, @/components/ui/, @/types/, @/features/B (barrel only)Feature B internals
Feature internalsSibling files within same feature, @/lib/, @/components/ui/Other feature internals

Thin Pages

Pages in src/app/(dashboard)/ are async Server Components that only:

  1. Call await prefetchXxx() for SSR data
  2. Wrap children with <Hydrate> from @/lib/hydrate
  3. Render feature components

See examples/thin-page.tsx for the pattern.

// ✅ CORRECT — thin page
import { Hydrate } from "@/lib/hydrate";
import { TaskList, prefetchTasks } from "@/features/tasks";

export default async function TasksPage() {
  await prefetchTasks();
  return (
    <Hydrate>
      <TaskList />
    </Hydrate>
  );
}
// ❌ WRONG — page with business logic
import { apiClient } from "@/lib/api-client";

export default async function TasksPage() {
  const tasks = await apiClient("/api/tasks"); // ❌ fetching directly in page
  const filtered = tasks.filter(t => t.status === "active"); // ❌ business logic in page
  return <div>{filtered.map(t => <p key={t.id}>{t.title}</p>)}</div>; // ❌ rendering directly
}

Navigation

When a feature needs a nav entry, add it to src/features/shell/constants/navigation.ts. The shell feature owns all navigation.

Naming Conventions

ElementConventionExample
Feature folderskebab-case singulartask-management/
ComponentsPascalCase export, kebab-case filetask-card.tsxTaskCard
HookscamelCase with use prefixuse-task-filters.tsuseTaskFilters
Stores[name]-store.ts in stores/useTaskStore
Schemas[name].schema.ts in schemas/taskSchema
Mocks[name].mock.ts in mocks/createMockTask
ConstantsUPPER_SNAKE_CASETASK_STATUS
Query filesuse-[resource].ts in queries/use-tasks.ts
Type files[name].ts in types/task.ts
Test files[name].test.ts(x) co-locatedtask-card.test.tsx

Creating a New Feature — Step by Step

  1. Create the feature folder: src/features/[feature-name]/
  2. Create Zod schemas first: schemas/[name].schema.ts
  3. Infer types: types/[name].ts with z.infer<typeof schema>
  4. Create components: components/[name].tsx
  5. Create query file (if data fetching needed): queries/use-[resource].ts
  6. Create mock factories: mocks/[name].mock.ts
  7. Create stores (if client-only UI state needed): stores/[name]-store.ts
  8. Create barrel export: index.ts
  9. Create thin page: src/app/(dashboard)/[feature]/page.tsx
  10. Add nav item: src/features/shell/constants/navigation.ts

DO NOT

  • DO NOT create loose components, hooks, or types outside of a feature — all domain code goes in src/features/.
  • DO NOT import from feature internals — always use the barrel index.ts.
  • DO NOT put business logic in pages — pages are thin (prefetch + Hydrate + render).
  • DO NOT put Zustand stores in hooks/ — stores go in stores/[name]-store.ts.
  • DO NOT put provider logic directly in layout files — providers go in src/lib/providers/.
  • DO NOT modify shadcn/ui files in src/components/ui/ — use them as-is.
  • DO NOT hand-write TypeScript interfaces — use Zod schemas with z.infer.
  • DO NOT create a feature without a barrel index.ts — every feature needs one.
  • DO NOT forget to add feedback states (EmptyState, ErrorState, LoadingState) — never return null for empty data.
Skills Info
Original Name:feature-architectureAuthor:madlado87