← Назад к вопросам

Как FSD сочетается с Next.js?

1.7 Middle🔥 171 комментариев
#JavaScript Core#Браузер и сетевые технологии

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как 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

  1. Масштабируемость - легко добавлять новые features

  2. Modularity - каждый feature независим

  3. Separation of Concerns - чёткое разделение ответственности

  4. Tree-shaking - неиспользуемый код легко удалить

  5. Testing - каждый слой легко тестируется

Важные моменты

  • Server Components (app/ в Next.js 13+) хорошо работают с FSD, так как логика на бэке отделена от UI
  • API маршруты можно организовать отдельно в app/api/ или в features
  • Зависимости идут только внутрь - feature не может импортировать из другого feature, только из shared и entities
  • Барельная в импортах - используй index.ts для чистого экспорта

Вывод: FSD отлично работает с Next.js и помогает создавать масштабируемые приложения с чёткой архитектурой. Главное - соблюдать правило направления зависимостей и избегать циклических импортов.

Как FSD сочетается с Next.js? | PrepBro