Как FSD сочетается с Next.js?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как FSD сочетается с Next.js?
FSD (Feature-Sliced Design) - это архитектурная методология, которая хорошо совместима с Next.js. FSD предлагает структурировать приложение слоями и срезами (features), что идеально подходит для масштабируемых Next.js проектов.
Структура FSD
FSD делит приложение на несколько слоёв (от внешних к внутренним):
- app - инициализация приложения, глобальная конфигурация
- pages - страницы (Next.js Page Router или App Router)
- widgets - композитные компоненты (составлены из features)
- features - пользовательские функции (auth, comments, likes)
- entities - бизнес-сущности (User, Post, Comment)
- shared - переиспользуемый код (UI, utils, hooks, types)
Сочетание FSD с Next.js App Router
В современном Next.js (13+) с App Router структура выглядит так:
project/
├── app/
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Home page
│ └── (auth)/
│ ├── layout.tsx # Auth layout
│ ├── login/
│ │ └── page.tsx
│ └── register/
│ └── page.tsx
│
├── src/
│ ├── app/ # FSD слой app
│ │ └── AppProvider.tsx
│ │
│ ├── pages/ # FSD слой pages (компоненты для app/)
│ │ ├── Home/
│ │ │ ├── Home.tsx
│ │ │ └── Home.module.css
│ │ └── ProfilePage/
│ │ ├── ProfilePage.tsx
│ │ └── ProfilePage.module.css
│ │
│ ├── widgets/ # Композитные компоненты
│ │ ├── Header/
│ │ │ ├── ui/
│ │ │ │ ├── Header.tsx
│ │ │ │ └── Header.module.css
│ │ │ ├── Header.tsx # Экспорт
│ │ │ └── index.ts
│ │ └── Sidebar/
│ │ ├── ui/
│ │ ├── Sidebar.tsx
│ │ └── index.ts
│ │
│ ├── features/ # Функции (auth, comments, likes)
│ │ ├── auth/
│ │ │ ├── model/
│ │ │ │ ├── authSlice.ts # Redux slice
│ │ │ │ ├── useAuth.ts # Хук
│ │ │ │ └── authAPI.ts # API
│ │ │ ├── ui/
│ │ │ │ ├── LoginForm.tsx
│ │ │ │ └── LogoutButton.tsx
│ │ │ └── index.ts
│ │ │
│ │ ├── comments/
│ │ │ ├── model/
│ │ │ │ ├── useComments.ts
│ │ │ │ └── commentsAPI.ts
│ │ │ ├── ui/
│ │ │ │ ├── CommentForm.tsx
│ │ │ │ ├── CommentList.tsx
│ │ │ │ └── CommentCard.tsx
│ │ │ └── index.ts
│ │ │
│ │ └── likes/
│ │ ├── model/
│ │ │ └── useLikes.ts
│ │ ├── ui/
│ │ │ └── LikeButton.tsx
│ │ └── index.ts
│ │
│ ├── entities/ # Бизнес-сущности
│ │ ├── User/
│ │ │ ├── model/
│ │ │ │ ├── User.types.ts
│ │ │ │ └── userAPI.ts
│ │ │ ├── ui/
│ │ │ │ └── UserCard.tsx
│ │ │ └── index.ts
│ │ │
│ │ ├── Post/
│ │ │ ├── model/
│ │ │ │ ├── Post.types.ts
│ │ │ │ └── postAPI.ts
│ │ │ ├── ui/
│ │ │ │ └── PostCard.tsx
│ │ │ └── index.ts
│ │ │
│ │ └── Comment/
│ │ ├── model/
│ │ │ ├── Comment.types.ts
│ │ │ └── commentAPI.ts
│ │ ├── ui/
│ │ │ └── CommentItem.tsx
│ │ └── index.ts
│ │
│ └── shared/ # Переиспользуемый код
│ ├── ui/ # UI компоненты
│ │ ├── Button/
│ │ │ ├── Button.tsx
│ │ │ ├── Button.module.css
│ │ │ └── index.ts
│ │ ├── Input/
│ │ │ ├── Input.tsx
│ │ │ └── index.ts
│ │ └── Modal/
│ │ ├── Modal.tsx
│ │ └── index.ts
│ │
│ ├── lib/ # Утилиты
│ │ ├── api.ts
│ │ ├── utils.ts
│ │ └── cn.ts
│ │
│ ├── hooks/ # Переиспользуемые хуки
│ │ ├── useMediaQuery.ts
│ │ ├── useClickOutside.ts
│ │ └── useFetch.ts
│ │
│ ├── types/ # Глобальные типы
│ │ └── common.ts
│ │
│ ├── config/ # Конфигурация
│ │ ├── env.ts
│ │ └── routes.ts
│ │
│ └── constants/ # Константы
│ ├── api.ts
│ └── ui.ts
│
└── package.json
Пример реализации в Next.js
app/page.tsx (Next.js страница):
import { HomePage } from '@/pages/Home';
import { Header } from '@/widgets/Header';
export default function Home() {
return (
<>
<Header />
<HomePage />
</>
);
}
app/posts/[id]/page.tsx (динамический маршрут):
import { PostPage } from '@/pages/PostPage';
export async function generateMetadata({ params }: { params: { id: string } }) {
const post = await fetch(`/api/posts/${params.id}`).then(r => r.json());
return {
title: post.title,
description: post.excerpt,
};
}
export default function Page({ params }: { params: { id: string } }) {
return <PostPage postId={params.id} />;
}
src/pages/PostPage/PostPage.tsx:
'use client';
import { useEffect, useState } from 'react';
import { CommentList } from '@/features/comments';
import { PostCard } from '@/entities/Post';
import { Post as PostType } from '@/entities/Post/model/Post.types';
export function PostPage({ postId }: { postId: string }) {
const [post, setPost] = useState<PostType | null>(null);
useEffect(() => {
fetch(`/api/posts/${postId}`)
.then(r => r.json())
.then(setPost);
}, [postId]);
if (!post) return <div>Loading...</div>;
return (
<div className="post-page">
<PostCard post={post} />
<CommentList postId={postId} />
</div>
);
}
src/features/comments/ui/CommentForm.tsx:
'use client';
import { useState } from 'react';
import { Button } from '@/shared/ui/Button';
import { useComments } from '../model/useComments';
interface CommentFormProps {
postId: string;
onCommentAdded: () => void;
}
export function CommentForm({ postId, onCommentAdded }: CommentFormProps) {
const [text, setText] = useState('');
const { addComment, isLoading } = useComments();
const handleSubmit = async () => {
await addComment({ postId, text });
setText('');
onCommentAdded();
};
return (
<form onSubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Add a comment..."
/>
<Button type="submit" disabled={isLoading}>
Submit
</Button>
</form>
);
}
src/shared/ui/Button/Button.tsx:
import { ButtonHTMLAttributes } from 'react';
import { cn } from '@/shared/lib/cn';
import styles from './Button.module.css';
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
}
export function Button({
className,
variant = 'primary',
size = 'md',
...props
}: ButtonProps) {
return (
<button
className={cn(
styles.button,
styles[variant],
styles[size],
className
)}
{...props}
/>
);
}
Преимущества FSD + Next.js
-
Масштабируемость - легко добавлять новые features
-
Modularity - каждый feature независим
-
Separation of Concerns - чёткое разделение ответственности
-
Tree-shaking - неиспользуемый код легко удалить
-
Testing - каждый слой легко тестируется
Важные моменты
- Server Components (app/ в Next.js 13+) хорошо работают с FSD, так как логика на бэке отделена от UI
- API маршруты можно организовать отдельно в
app/api/или в features - Зависимости идут только внутрь - feature не может импортировать из другого feature, только из shared и entities
- Барельная в импортах - используй index.ts для чистого экспорта
Вывод: FSD отлично работает с Next.js и помогает создавать масштабируемые приложения с чёткой архитектурой. Главное - соблюдать правило направления зависимостей и избегать циклических импортов.