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 /app directory 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:
-
/app directory (root level): Next.js routing only, minimal logic
-
Contains page.tsx , layout.tsx , route groups
-
Imports and renders from /src/views
-
/src/views layer (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 slice
-
widgets/header
-
Header widget slice
-
features/auth
-
Authentication feature slice
-
entities/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/
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 /app to /src/views
-
Keep routing in /app , business logic in /src/views
-
Compose widgets in views
Configure app layer
-
Move global providers to app/providers/
-
Move global styles to app/styles/
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 .next directory: 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