feature-sliced design
npx skills add https://github.com/constellos/claude-code-plugins --skill Feature-Sliced Design
Skill 文档
Feature-Sliced Design
Purpose
Feature-Sliced Design (FSD) is an architectural methodology for organizing frontend applications into a standardized, scalable structure. It provides clear separation of concerns through a layered hierarchy that prevents circular dependencies and promotes maintainability.
Why use FSD:
- Scalability: Grows naturally as your application expands
- Maintainability: Clear boundaries make refactoring safer
- Team collaboration: Consistent structure enables parallel development
- Onboarding: New developers understand architecture quickly
Custom ‘views’ layer:
This skill uses ‘views’ instead of the standard FSD ‘pages’ layer to avoid confusion with Next.js App Router’s /app directory. The /app directory handles routing only (minimal logic), while /src/views contains your actual page business logic.
Next.js integration:
FSD works seamlessly with Next.js App Router by separating routing concerns (in /app) from business logic (in /src/views and other FSD layers). This keeps your routing configuration clean while maintaining FSD’s architectural benefits.
When to Use
Apply Feature-Sliced Design when:
- Starting new Next.js projects that require clear architectural boundaries
- Refactoring growing codebases that lack consistent structure
- Working with multi-developer teams needing standardized organization
- Building applications with complex business logic requiring separation of concerns
- Developing Turborepo monorepo applications where each app needs independent FSD structure
- Scaling applications where circular dependencies become problematic
- Creating enterprise applications with long-term maintenance requirements
Core Principles
Layer Hierarchy
FSD organizes code into 7 standardized layers (from highest to lowest):
- app – Application initialization, global providers, routing configuration
- processes – Deprecated (functionality moved to features and app)
- views – Page-level business logic (custom naming, replaces standard ‘pages’)
- widgets – Large composite UI blocks that span multiple features
- features – User-facing interactions with business value
- entities – Business domain objects and models
- shared – Reusable utilities, UI kit, third-party integrations
Import rule: A module can only import from layers strictly below it in the hierarchy.
âââââââââââââââââââ
â app â â Can import from all layers below
âââââââââââââââââââ¤
â views â â Can import: widgets, features, entities, shared
âââââââââââââââââââ¤
â widgets â â Can import: features, entities, shared
âââââââââââââââââââ¤
â features â â Can import: entities, shared
âââââââââââââââââââ¤
â entities â â Can import: shared only
âââââââââââââââââââ¤
â shared â â Cannot import from any FSD layer
âââââââââââââââââââ
This hierarchy prevents circular dependencies and ensures clear architectural boundaries.
‘Views’ vs ‘Pages’ Layer
Why ‘views’ instead of ‘pages’:
- Next.js uses
/appdirectory for routing (App Router) - Standard FSD uses ‘pages’ layer for page business logic
- Using ‘views’ eliminates confusion between routing (
/app) and business logic (/src/views)
Separation of concerns:
/appdirectory (root level): Next.js routing only, minimal logic- Contains
page.tsx,layout.tsx, route groups - Imports and renders from
/src/views
- Contains
/src/viewslayer (FSD): Page business logic, component composition- Contains view components, models, API calls
- Composes widgets, features, entities
This separation keeps routing configuration clean while maintaining FSD architectural principles.
Slices
Slices are domain-based partitions within layers (except app and shared, which have no slices).
Examples:
views/dashboard– Dashboard page slicewidgets/header– Header widget slicefeatures/auth– Authentication feature sliceentities/user– User entity slice
Public API pattern:
Each slice exports through index.ts to control its public interface:
// src/features/auth/index.ts
export { LoginForm } from './ui/LoginForm';
export { useAuth } from './model/useAuth';
export type { AuthState } from './model/types';
// Internal implementation details NOT exported
This prevents deep imports and maintains encapsulation.
Segments
Segments are purpose-based groupings within slices:
- ui/ – React components, visual elements
- model/ – Business logic, state management, TypeScript types
- api/ – API clients, data fetching, external integrations
- lib/ – Utility functions, helpers specific to the slice
- config/ – Configuration constants, feature flags
Example structure:
features/
âââ auth/
âââ ui/
â âââ LoginForm.tsx
â âââ SignupForm.tsx
âââ model/
â âââ useAuth.ts
â âââ types.ts
âââ api/
â âââ authApi.ts
âââ index.ts
FSD with Next.js App Router
Routing Architecture
Next.js App Router uses /app directory for routing. FSD layers live in /src directory.
File organization:
my-nextjs-app/
âââ app/ # Next.js routing (minimal logic)
â âââ layout.tsx # Root layout
â âââ page.tsx # Home route
â âââ dashboard/
â â âââ page.tsx # Dashboard route
â âââ settings/
â âââ page.tsx # Settings route
â
âââ src/ # FSD layers
â âââ views/ # Page business logic
â âââ home/
â â âââ ui/
â â â âââ HomeView.tsx
â â âââ index.ts
â âââ dashboard/
â â âââ ui/
â â â âââ DashboardView.tsx
â â âââ model/
â â â âââ useDashboard.ts
â â âââ index.ts
â âââ settings/
â âââ ui/
â â âââ SettingsView.tsx
â âââ index.ts
Routing pages import from views:
// app/dashboard/page.tsx - Routing only
import { DashboardView } from '@/views/dashboard';
export default function DashboardPage() {
return <DashboardView />;
}
// src/views/dashboard/ui/DashboardView.tsx - Business logic
import { Header } from '@/widgets/header';
import { StatsCard } from '@/features/analytics';
export function DashboardView() {
return (
<div>
<Header />
<StatsCard />
</div>
);
}
Standalone Next.js Structure
Complete FSD structure for a standalone Next.js application:
my-nextjs-app/
âââ app/ # Next.js App Router
â âââ layout.tsx # Root layout
â âââ page.tsx # Home route
â âââ (auth)/ # Route group
â â âââ login/
â â â âââ page.tsx
â â âââ signup/
â â âââ page.tsx
â âââ dashboard/
â â âââ page.tsx
â âââ api/ # API routes
â â âââ users/
â â âââ route.ts
â âââ not-found.tsx
â
âââ src/
â âââ app/ # App layer (no slices)
â â âââ providers/
â â â âââ AuthProvider.tsx
â â â âââ QueryProvider.tsx
â â âââ styles/
â â â âââ globals.css
â â âââ config/
â â âââ constants.ts
â â
â âââ views/ # Views layer (page logic)
â â âââ home/
â â âââ dashboard/
â â âââ login/
â â âââ signup/
â â
â âââ widgets/ # Widgets layer
â â âââ header/
â â âââ sidebar/
â â âââ footer/
â â âââ notification-panel/
â â
â âââ features/ # Features layer
â â âââ auth/
â â âââ search/
â â âââ theme-toggle/
â â âââ user-profile/
â â
â âââ entities/ # Entities layer
â â âââ user/
â â âââ post/
â â âââ comment/
â â âââ session/
â â
â âââ shared/ # Shared layer (no slices)
â âââ ui/ # UI components
â â âââ button/
â â âââ input/
â â âââ card/
â âââ lib/ # Utilities
â â âââ format.ts
â â âââ validation.ts
â âââ api/ # API client
â â âââ client.ts
â âââ config/
â âââ env.ts
â
âââ public/
â âââ images/
â âââ fonts/
â
âââ package.json
Turborepo Monorepo Structure
FSD structure within a Turborepo monorepo where each app has independent FSD organization:
turborepo-root/
âââ apps/
â âââ web/ # Consumer-facing app
â â âââ app/ # Next.js routing
â â â âââ layout.tsx
â â â âââ page.tsx
â â â âââ shop/
â â â âââ page.tsx
â â âââ src/ # Independent FSD structure
â â â âââ app/
â â â âââ views/
â â â â âââ home/
â â â â âââ shop/
â â â âââ widgets/
â â â â âââ product-grid/
â â â â âââ shopping-cart/
â â â âââ features/
â â â â âââ add-to-cart/
â â â â âââ checkout/
â â â âââ entities/
â â â â âââ product/
â â â â âââ order/
â â â âââ shared/
â â âââ package.json
â â
â âââ admin/ # Admin dashboard app
â âââ app/ # Next.js routing
â â âââ layout.tsx
â â âââ page.tsx
â â âââ products/
â â âââ page.tsx
â âââ src/ # Independent FSD structure
â â âââ app/
â â âââ views/
â â â âââ dashboard/
â â â âââ products/
â â âââ widgets/
â â â âââ admin-header/
â â â âââ stats-panel/
â â âââ features/
â â â âââ product-editor/
â â â âââ user-management/
â â âââ entities/
â â â âââ product/
â â â âââ admin/
â â âââ shared/
â âââ package.json
â
âââ packages/ # Optional shared packages
â âââ ui/ # Shared UI components (can mirror shared/ui)
â â âââ button/
â â âââ input/
â âââ utils/ # Shared utilities
â â âââ validation.ts
â âââ types/ # Shared TypeScript types
â âââ common.ts
â
âââ turbo.json
âââ package.json
Key Turborepo principles:
- Each app (
web,admin) has its own complete FSD structure - Apps are independent – no cross-app imports from FSD layers
- Shared code goes in
packages/directory (optional) - Use
workspace:*protocol for package dependencies
Layer Definitions
app Layer
Purpose: Application-wide setup, initialization, and global configuration.
Responsibilities:
- Global providers (theme, auth, query client)
- Root styles and CSS imports
- Application-level configuration
- Error boundaries
Import rules: Can import from all layers below (views, widgets, features, entities, shared).
No slices: The app layer contains segments directly (providers/, styles/, config/).
Example:
// src/app/providers/Providers.tsx
'use client';
import { QueryClientProvider } from '@tanstack/react-query';
import { queryClient } from '@/shared/api/queryClient';
import { AuthProvider } from '@/features/auth';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
<AuthProvider>
{children}
</AuthProvider>
</QueryClientProvider>
);
}
// app/layout.tsx
import { Providers } from '@/app/providers/Providers';
import '@/app/styles/globals.css';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
views Layer
Purpose: Page-level business logic and component composition.
Responsibilities:
- Compose widgets, features, and entities into complete pages
- Page-specific state management
- Data fetching for the page
- SEO metadata
Import rules: Can import from widgets, features, entities, shared.
Has slices: Each page gets its own slice (e.g., views/dashboard, views/settings).
Example:
// src/views/dashboard/ui/DashboardView.tsx
import { Header } from '@/widgets/header';
import { Sidebar } from '@/widgets/sidebar';
import { StatsCard } from '@/features/analytics';
import { RecentActivity } from '@/features/activity';
import { User } from '@/entities/user';
interface DashboardViewProps {
user: User;
}
export function DashboardView({ user }: DashboardViewProps) {
return (
<div className="dashboard">
<Header user={user} />
<div className="dashboard-content">
<Sidebar />
<main>
<StatsCard userId={user.id} />
<RecentActivity userId={user.id} />
</main>
</div>
</div>
);
}
// src/views/dashboard/index.ts
export { DashboardView } from './ui/DashboardView';
// app/dashboard/page.tsx
import { DashboardView } from '@/views/dashboard';
import { getCurrentUser } from '@/entities/user';
export default async function DashboardPage() {
const user = await getCurrentUser();
return <DashboardView user={user} />;
}
widgets Layer
Purpose: Large, self-contained composite UI blocks that combine multiple features.
Responsibilities:
- Reusable across multiple pages
- Compose multiple features together
- Complex UI layouts (headers, sidebars, footers)
- Navigation components
Import rules: Can import from features, entities, shared.
Has slices: Each widget gets its own slice (e.g., widgets/header, widgets/sidebar).
Example:
// src/widgets/header/ui/Header.tsx
import { SearchBar } from '@/features/search';
import { UserMenu } from '@/features/user-menu';
import { NotificationBell } from '@/features/notifications';
import { User } from '@/entities/user';
import { Logo } from '@/shared/ui/logo';
interface HeaderProps {
user: User;
}
export function Header({ user }: HeaderProps) {
return (
<header className="header">
<Logo />
<SearchBar />
<div className="header-actions">
<NotificationBell userId={user.id} />
<UserMenu user={user} />
</div>
</header>
);
}
// src/widgets/header/index.ts
export { Header } from './ui/Header';
features Layer
Purpose: User-facing interactions and business logic with clear business value.
Responsibilities:
- Specific user actions (login, add to cart, like post)
- Feature-specific state management
- Business logic and validation
- API interactions for the feature
Import rules: Can import from entities, shared.
Has slices: Each feature gets its own slice (e.g., features/auth, features/search).
Example:
// src/features/auth/model/types.ts
export interface LoginCredentials {
email: string;
password: string;
}
// src/features/auth/api/login.ts
import { User } from '@/entities/user';
import { apiClient } from '@/shared/api/client';
import type { LoginCredentials } from '../model/types';
export async function login(credentials: LoginCredentials): Promise<User> {
const response = await apiClient.post('/auth/login', credentials);
return response.data;
}
// src/features/auth/ui/LoginForm.tsx
'use client';
import { useState } from 'react';
import { login } from '../api/login';
import { Button } from '@/shared/ui/button';
import { Input } from '@/shared/ui/input';
export function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await login({ email, password });
};
return (
<form onSubmit={handleSubmit}>
<Input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<Input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<Button type="submit">Login</Button>
</form>
);
}
// src/features/auth/index.ts
export { LoginForm } from './ui/LoginForm';
export { login } from './api/login';
export type { LoginCredentials } from './model/types';
entities Layer
Purpose: Business domain objects and core data models.
Responsibilities:
- Data structures representing business concepts
- Entity-specific utilities
- Base API operations (CRUD)
- Type definitions
Import rules: Can import from shared only.
Has slices: Each entity gets its own slice (e.g., entities/user, entities/post).
Example:
// src/entities/user/model/types.ts
export interface User {
id: string;
name: string;
email: string;
avatar?: string;
role: 'admin' | 'user';
}
// src/entities/user/api/getUser.ts
import { apiClient } from '@/shared/api/client';
import type { User } from '../model/types';
export async function getUser(id: string): Promise<User> {
const response = await apiClient.get(`/users/${id}`);
return response.data;
}
export async function getCurrentUser(): Promise<User> {
const response = await apiClient.get('/users/me');
return response.data;
}
// src/entities/user/ui/UserCard.tsx
import type { User } from '../model/types';
import { Avatar } from '@/shared/ui/avatar';
interface UserCardProps {
user: User;
}
export function UserCard({ user }: UserCardProps) {
return (
<div className="user-card">
<Avatar src={user.avatar} alt={user.name} />
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
</div>
);
}
// src/entities/user/index.ts
export { UserCard } from './ui/UserCard';
export { getUser, getCurrentUser } from './api/getUser';
export type { User } from './model/types';
shared Layer
Purpose: Reusable utilities, UI components, and third-party integrations.
Responsibilities:
- UI kit (button, input, card components)
- Helper functions (formatters, validators)
- API client configuration
- Constants and environment variables
- Third-party library integrations
Import rules: Cannot import from any FSD layer (only external packages).
No slices: Contains segments directly (ui/, lib/, api/, config/).
Example:
// src/shared/ui/button/Button.tsx
import { type ButtonHTMLAttributes } from 'react';
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'ghost';
size?: 'sm' | 'md' | 'lg';
}
export function Button({
variant = 'primary',
size = 'md',
className,
children,
...props
}: ButtonProps) {
return (
<button
className={`button button--${variant} button--${size} ${className}`}
{...props}
>
{children}
</button>
);
}
// src/shared/lib/format.ts
export function formatDate(date: Date): string {
return new Intl.DateTimeFormat('en-US').format(date);
}
export function formatCurrency(amount: number): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amount);
}
// src/shared/api/client.ts
import axios from 'axios';
export const apiClient = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
headers: {
'Content-Type': 'application/json',
},
});
// src/shared/config/env.ts
export const env = {
apiUrl: process.env.NEXT_PUBLIC_API_URL!,
nodeEnv: process.env.NODE_ENV,
} as const;
Workflow
Step 1: Set Up Layer Directories
Create the FSD folder structure:
mkdir -p src/{app,views,widgets,features,entities,shared}
mkdir -p src/app/{providers,styles,config}
mkdir -p src/shared/{ui,lib,api,config}
Step 2: Create First Entity
Start with entities (bottom layer). Define your core business models:
// src/entities/user/model/types.ts
export interface User {
id: string;
name: string;
email: string;
}
// src/entities/user/api/getUser.ts
export async function getUser(id: string): Promise<User> {
// API implementation
}
// src/entities/user/index.ts
export type { User } from './model/types';
export { getUser } from './api/getUser';
Step 3: Build Features Using Entities
Create features that use entities:
// src/features/user-profile/ui/UserProfile.tsx
import { User } from '@/entities/user'; // â
Feature imports entity
export function UserProfile({ user }: { user: User }) {
return <div>{user.name}</div>;
}
// src/features/user-profile/index.ts
export { UserProfile } from './ui/UserProfile';
Step 4: Compose Widgets from Features
Build composite widgets:
// src/widgets/header/ui/Header.tsx
import { UserProfile } from '@/features/user-profile'; // â
Widget imports feature
import { SearchBar } from '@/features/search';
export function Header({ user }) {
return (
<header>
<SearchBar />
<UserProfile user={user} />
</header>
);
}
Step 5: Assemble Views
Create page-level views:
// src/views/dashboard/ui/DashboardView.tsx
import { Header } from '@/widgets/header'; // â
View imports widget
export function DashboardView() {
return (
<div>
<Header />
{/* More content */}
</div>
);
}
// src/views/dashboard/index.ts
export { DashboardView } from './ui/DashboardView';
Step 6: Connect to App Router
Wire views to Next.js routing:
// app/dashboard/page.tsx
import { DashboardView } from '@/views/dashboard';
export default function DashboardPage() {
return <DashboardView />;
}
Import Rules and Dependencies
Allowed Import Patterns
// â
Layer importing from layer below
import { User } from '@/entities/user'; // Feature â Entity
import { LoginForm } from '@/features/auth'; // Widget â Feature
import { Header } from '@/widgets/header'; // View â Widget
// â
Any layer importing from shared
import { Button } from '@/shared/ui/button';
import { formatDate } from '@/shared/lib/format';
// â
Slice importing from different slice in lower layer
import { User } from '@/entities/user'; // features/auth â entities/user
import { Post } from '@/entities/post'; // features/like â entities/post
Forbidden Import Patterns
// â Layer importing from same or higher layer
import { DashboardView } from '@/views/dashboard'; // Feature â View (upward)
import { Header } from '@/widgets/header'; // Feature â Widget (upward)
import { LoginForm } from '@/features/login'; // features/auth â features/login (same layer)
// â Cross-slice imports within same layer
import { SearchBar } from '@/features/search'; // features/auth â features/search
// â Shared importing from FSD layers
import { User } from '@/entities/user'; // shared/lib â entities/user
Valid vs Invalid Examples
Invalid (cross-feature import):
// â src/features/search/ui/SearchBar.tsx
import { LoginForm } from '@/features/auth'; // Same layer import
Valid (extract to widget):
// â
src/widgets/navbar/ui/Navbar.tsx
import { SearchBar } from '@/features/search';
import { LoginForm } from '@/features/auth';
export function Navbar() {
return (
<nav>
<SearchBar />
<LoginForm />
</nav>
);
}
Fixing Circular Dependencies
Problem:
// features/auth imports features/user-settings
// features/user-settings imports features/auth
// â Circular dependency
Solution 1: Extract to entity
// Move shared logic to entities/user
// Both features import from entities/user
// â
No circular dependency
Solution 2: Extract to widget
// Create widgets/user-panel that imports both features
// â
Widget layer can import from features
Public API Enforcement
Always use index.ts to control exports:
// src/features/auth/index.ts
export { LoginForm } from './ui/LoginForm';
export { useAuth } from './model/useAuth';
export type { AuthState } from './model/types';
// â Do NOT export internal helpers
// export { validatePassword } from './lib/validation'; // Keep internal
Import from public API only:
// â
Correct
import { LoginForm } from '@/features/auth';
// â Wrong (deep import)
import { LoginForm } from '@/features/auth/ui/LoginForm';
Segment Patterns
ui/ Segment
Purpose: React components and visual elements.
When to use:
- Any React component
- UI composition
- Visual presentation
Example:
// src/entities/user/ui/UserCard.tsx
import type { User } from '../model/types';
export function UserCard({ user }: { user: User }) {
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
}
model/ Segment
Purpose: Business logic, state management, and type definitions.
When to use:
- TypeScript interfaces and types
- React hooks for state
- Business logic functions
- Data transformations
Example:
// src/features/auth/model/useAuth.ts
'use client';
import { create } from 'zustand';
import type { User } from '@/entities/user';
interface AuthState {
user: User | null;
isAuthenticated: boolean;
login: (user: User) => void;
logout: () => void;
}
export const useAuth = create<AuthState>((set) => ({
user: null,
isAuthenticated: false,
login: (user) => set({ user, isAuthenticated: true }),
logout: () => set({ user: null, isAuthenticated: false }),
}));
api/ Segment
Purpose: API clients, data fetching, and external integrations.
When to use:
- HTTP requests
- WebSocket connections
- Third-party API integrations
- Server actions (Next.js)
Example:
// src/entities/user/api/userApi.ts
'use server';
import { apiClient } from '@/shared/api/client';
import type { User } from '../model/types';
export async function fetchUsers(): Promise<User[]> {
const response = await apiClient.get('/users');
return response.data;
}
export async function createUser(data: Omit<User, 'id'>): Promise<User> {
const response = await apiClient.post('/users', data);
return response.data;
}
lib/ Segment
Purpose: Utility functions and helpers specific to the slice.
When to use:
- Slice-specific utilities
- Validation functions
- Data transformation helpers
Example:
// src/features/auth/lib/validation.ts
import { z } from 'zod';
export const loginSchema = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
});
export function validateLogin(data: unknown) {
return loginSchema.parse(data);
}
config/ Segment
Purpose: Configuration constants and feature flags.
When to use:
- Feature-specific constants
- Configuration objects
- Feature flags
Example:
// src/app/config/theme.ts
export const theme = {
colors: {
primary: '#0070f3',
secondary: '#ff4081',
},
breakpoints: {
sm: '640px',
md: '768px',
lg: '1024px',
},
} as const;
Migration Strategy
Migrating Existing Next.js App to FSD
Bottom-up approach (recommended):
-
Start with shared layer
- Extract common UI components to
shared/ui/ - Move utilities to
shared/lib/ - Configure API client in
shared/api/
- Extract common UI components to
-
Define entities
- Identify business domain objects (User, Post, Comment)
- Create entity types in
entities/{name}/model/ - Move CRUD operations to
entities/{name}/api/
-
Extract features
- Identify user interactions (login, search, like)
- Create feature slices in
features/{name}/ - Use entities within features
-
Build widgets
- Identify composite UI blocks (Header, Sidebar)
- Create widget slices in
widgets/{name}/ - Compose features within widgets
-
Organize views
- Move page logic from
/appto/src/views - Keep routing in
/app, business logic in/src/views - Compose widgets in views
- Move page logic from
-
Configure app layer
- Move global providers to
app/providers/ - Move global styles to
app/styles/
- Move global providers to
Handling Existing Code
Incremental migration:
- Migrate one page at a time
- Start with least complex pages
- Use both old and new structure during transition
- Update imports as you migrate
Testing throughout:
- Run tests after each layer migration
- Ensure no functionality breaks
- Verify import paths are correct
Best Practices
Keep slices isolated:
- No cross-slice imports within the same layer
- Each slice should be independent
- Extract shared logic to lower layers
Use Public API pattern:
- Always export through
index.ts - Prevents deep imports
- Makes refactoring easier
Colocate tests:
features/
âââ auth/
âââ ui/
â âââ LoginForm.tsx
â âââ LoginForm.test.tsx # Test next to implementation
âââ index.ts
Avoid “god slices”:
- Keep slices focused on single responsibility
- Split large slices into multiple smaller ones
- Extract common logic to shared layer
Name by domain, not tech:
- â
features/product-search - â
features/search-bar-component
Use TypeScript strict mode:
{
"compilerOptions": {
"strict": true
}
}
Document architecture decisions:
- Keep ADR (Architecture Decision Records)
- Document why certain slices exist
- Explain layer boundary decisions
Common Patterns
Shared UI Components
// src/shared/ui/button/Button.tsx
export function Button({ children, ...props }) {
return <button {...props}>{children}</button>;
}
// Usage in feature
import { Button } from '@/shared/ui/button';
API Client Setup
// src/shared/api/client.ts
import axios from 'axios';
export const apiClient = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
});
// Usage in entity
import { apiClient } from '@/shared/api/client';
Form Handling with Features
// src/features/product-form/ui/ProductForm.tsx
'use client';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { productSchema } from '../lib/validation';
import { createProduct } from '../api/createProduct';
export function ProductForm() {
const { register, handleSubmit } = useForm({
resolver: zodResolver(productSchema),
});
return <form onSubmit={handleSubmit(createProduct)}>...</form>;
}
Authentication Integration
// src/features/auth/model/useAuth.ts
export const useAuth = create<AuthState>((set) => ({...}));
// src/widgets/header/ui/Header.tsx
import { useAuth } from '@/features/auth';
export function Header() {
const { user } = useAuth();
return <header>Welcome, {user?.name}</header>;
}
Data Fetching with Server Components
// app/dashboard/page.tsx
import { DashboardView } from '@/views/dashboard';
import { getUser } from '@/entities/user';
export default async function DashboardPage() {
const user = await getUser('current');
return <DashboardView user={user} />;
}
// src/views/dashboard/ui/DashboardView.tsx
import type { User } from '@/entities/user';
export function DashboardView({ user }: { user: User }) {
return <div>Welcome, {user.name}</div>;
}
Troubleshooting
Circular Dependencies
Problem: Two slices import from each other.
Solution:
- Extract shared logic to a lower layer (usually entities or shared)
- Create a higher layer (widget) that imports both
- Review if one slice should actually be split into multiple slices
Import Path Issues
Problem: TypeScript cannot resolve @/ imports.
Solution: Configure path aliases in tsconfig.json:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/app/*": ["src/app/*"],
"@/views/*": ["src/views/*"],
"@/widgets/*": ["src/widgets/*"],
"@/features/*": ["src/features/*"],
"@/entities/*": ["src/entities/*"],
"@/shared/*": ["src/shared/*"]
}
}
}
Build Errors
Problem: Next.js cannot find modules after restructuring.
Solution:
- Clear
.nextdirectory:rm -rf .next - Reinstall dependencies:
npm install - Restart dev server:
npm run dev
Configuration
TypeScript Path Aliases
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/app/*": ["src/app/*"],
"@/views/*": ["src/views/*"],
"@/widgets/*": ["src/widgets/*"],
"@/features/*": ["src/features/*"],
"@/entities/*": ["src/entities/*"],
"@/shared/*": ["src/shared/*"]
}
},
"include": ["src", "app"]
}
ESLint Import Rules (Optional)
// .eslintrc.js
module.exports = {
rules: {
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['@/views/*', '@/widgets/*'],
message: 'Features cannot import from views or widgets',
},
],
},
],
},
};
References
- Official FSD Documentation – Complete methodology guide
- FSD with Next.js Guide – Next.js integration patterns
- FSD GitHub Repository – Source documentation
- Frontend Monorepo Architecture – Turborepo and FSD
- FSD Tutorial – Step-by-step implementation guide
- FSD Examples – Real-world applications using FSD