Использование с Next.js
FSD совместим с Next.js как в варианте App Router, так и в варианте Pages Router, если устранить главный конфликт — папки app
и pages
.
App Router
Конфликт между FSD и Next.js в слое app
Next.js предлагает использовать папку app
для определения маршрутов приложения. Он ожидает, что файлы в папке app
будут соответствовать маршрутам. Этот механизм маршрутизации не соответствует концепции FSD, потому что невозможно сохранить плоскую структуру слайсов.
Чтоб решить эту проблему, перенесите Next.js-овскую папку app
в корень проекта, а затем импортируйте FSD-страницы из src
, где располагаются слои FSD, в Next.js-овскую папку app
.
Вам также нужно будет добавить в корень проекта папку pages
, иначе Next.js будет пытаться использовать src/pages
в качестве Pages Router, даже если вы используете App Router, что приведёт к ошибкам при сборке проекта. Имеет смысл положить внутрь этой корневой папки pages
файл README.md
с описанием, почему эта папка нужна, даже когда она пустая.
├── app # Папка app (Next.js)
│ ├── api
│ │ └── get-example
│ │ └── route.ts
│ └── example
│ └── page.tsx
├── pages # Пустая папка pages (Next.js)
│ └── README.md
└── src
├── app
│ └── api-routes # API-маршруты
├── pages
│ └── example
│ ├── index.ts
│ └── ui
│ └── example.tsx
├── widgets
├── features
├── entities
└── shared
Пример ре-экспорта страницы из src/pages
в Next.js-овском app
:
export { ExamplePage as default, metadata } from '@/pages/example';
Middleware
Если вы используете middleware в проекте, оно обязательно должно располагаться в корне проекта рядом с Next.js-овскими папками app
и pages
.
Instrumentation
Файл instrumentation.js
позволяет отслеживать производительность и поведение вашего приложения. Если вы его используете, то он обязательно должен находиться в корне проекта по аналогии с middleware.js
Pages Router
Конфликт между FSD и Next.js в слое pages
Роуты страниц должны помещаться в папку pages
в корне проекта по аналогии с папкой app
для App Router. Структура внутри src
, где располагаются папки слоёв, остаётся без изменений.
├── pages # Папка pages (Next.js)
│ ├── _app.tsx
│ ├── api
│ │ └── example.ts # Ре-экспорт API-маршрутов
│ └── example
│ └── index.tsx
└── src
├── app
│ ├── custom-app
│ │ └── custom-app.tsx # Кастомный компонент App
│ └── api-routes
│ └── get-example-data.ts # API-маршрут
├── pages
│ └── example
│ ├── index.ts
│ └── ui
│ └── example.tsx
├── widgets
├── features
├── entities
└── shared
Пример ре-экспорта страницы из src/pages
в Next.js-овском pages
:
export { Example as default } from '@/pages/example';
Кастомный компонент _app
Вы можете поместить ваш кастомный компонент App либо в src/app/_app
либо в src/app/custom-app
:
import type { AppProps } from 'next/app';
export const MyApp = ({ Component, pageProps }: AppProps) => {
return (
<>
<p>My Custom App component</p>
<Component { ...pageProps } />
</>
);
};
export { App as default } from '@/app/custom-app';
Route Handlers (API-маршруты)
Используйте сегмент api-routes
в слое app
для работы c Route Handlers.
Будьте внимательны при написании бэкенд-кода в структуре FSD — FSD в первую очередь предназначен для фронтенда, и именно это люди будут ожидать в нём найти. Если вам нужно много эндпоинтов, попробуйте выделить их в отдельный пакет в монорепозитории.
- App Router
- Pages Router
import { getExamplesList } from '@/shared/db';
export const getExampleData = () => {
try {
const examplesList = getExamplesList();
return Response.json({ examplesList });
} catch {
return Response.json(null, {
status: 500,
statusText: 'Ouch, something went wrong',
});
}
};
export { getExampleData as GET } from '@/app/api-routes';
import type { NextApiRequest, NextApiResponse } from 'next';
const config = {
api: {
bodyParser: {
sizeLimit: '1mb',
},
},
maxDuration: 5,
};
const handler = (req: NextApiRequest, res: NextApiResponse<ResponseData>) => {
res.status(200).json({ message: 'Hello from FSD' });
};
export const getExampleData = { config, handler } as const;
export { getExampleData } from './get-example-data';
import { getExampleData } from '@/app/api-routes';
export const config = getExampleData.config;
export default getExampleData.handler;
Дополнительные рекомендации
- Используйте сегмент
db
в слоеshared
для описания запросов к БД и их дальнейшего использования в вышестоящих слоях. - Логику кэширования и ревалидации запросов лучше держать там же, где и сами запросы.