Usage with Next.js
FSD는 Next.js의 App Router와 Pages Router 양쪽에 적용할 수 있습니다.
다만 Next.js가 요구하는 app, pages 폴더는 FSD 구조와 분리해 두어야 합니다.
App Router
섹션 제목: “App Router”app layer에서 FSD와 Next.js의 충돌
섹션 제목: “app layer에서 FSD와 Next.js의 충돌”App Router에서 app 폴더는 라우트 구조로 사용됩니다. 반면 FSD에서 app 폴더는 애플리케이션 전역을 담당하는 layer입니다.
두 구조는 app 폴더의 역할이 다르고, 같은 위치에 둘 경우 구조 충돌이 발생합니다.
이 충돌을 피하려면 Next.js의 app 폴더는 프로젝트 루트에 두고, 라우팅에 필요한 파일만 배치합니다.
페이지는 src 하위의 FSD pages layer에 두고, 루트 app 폴더의 각 라우트 파일에서 이를 가져와 연결합니다.
프로젝트 루트에는 빈 pages 폴더도 함께 두어야 합니다. 그렇지 않으면 App Router를 사용하더라도 Next.js가 src/pages를 Pages Router로 인식하므로 빌드가 실패합니다.
또한 비어 있는 폴더이더라도 왜 필요한지 설명하는 README.md를 이 루트 pages 폴더 안에 함께 두는 것이 좋습니다.
디렉터리app App folder (Next.js)
디렉터리api
디렉터리get-example
- route.ts
디렉터리example
- page.tsx
디렉터리pages Empty pages folder (Next.js)
- README.md
디렉터리src
디렉터리app
- api-routes API routes
디렉터리pages
디렉터리example
- index.ts
디렉터리ui
- example.tsx
디렉터리widgets/
- …
디렉터리features/
- …
디렉터리entities/
- …
디렉터리shared/
- …
루트 app의 라우트 파일에는 페이지 코드가 들어 있지 않습니다. src/pages의 페이지 파일을 가져와 연결합니다.
export { ExamplePage as default, metadata } from '@/pages/example';Middleware
섹션 제목: “Middleware”middleware를 사용한다면 Next.js의 app, pages 폴더와 함께 프로젝트 루트에 둡니다.
Instrumentation
섹션 제목: “Instrumentation”instrumentation.js는 애플리케이션의 성능과 동작을 모니터링하는 파일입니다. middleware.js와 같은 이유로, 이 파일도 프로젝트 루트에 둡니다.
Pages Router
섹션 제목: “Pages Router”pages layer에서 FSD와 Next.js의 충돌
섹션 제목: “pages layer에서 FSD와 Next.js의 충돌”Pages Router도 App Router와 같은 원리로 분리합니다. 라우트 파일은 프로젝트 루트의 pages 폴더에 두고, 실제 페이지 파일은 src 하위의 FSD 구조에 유지합니다.
디렉터리pages Pages folder (Next.js)
- _app.tsx
디렉터리api
- example.ts API route re-export
디렉터리example
- index.tsx
디렉터리src
디렉터리app
디렉터리custom-app
- custom-app.tsx Custom App component
디렉터리api-routes
- get-example-data.ts API route
디렉터리pages
디렉터리example
- index.ts
디렉터리ui
- example.tsx
디렉터리widgets/
- …
디렉터리features/
- …
디렉터리entities/
- …
디렉터리shared/
- …
루트 pages의 각 파일도 src/pages의 페이지 파일을 가져와 연결합니다.
export { Example as default } from '@/pages/example';Custom _app 컴포넌트
섹션 제목: “Custom _app 컴포넌트”Custom App 컴포넌트는 src/app/_app 또는 src/app/custom-app에 둘 수 있습니다. 루트의 pages/_app.tsx는 이 컴포넌트를 가져와 연결합니다.
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 routes)
섹션 제목: “Route Handlers (API routes)”Route Handlers는 app layer의 api-routes segment에 둡니다. 루트의 라우트 파일은 여기서 가져와 연결합니다.
FSD는 프론트엔드 애플리케이션 구조를 위한 방법론입니다. API 코드가 많다면 같은 구조 안에 함께 두는 방식은 구조 파악을 어렵게 만들 수 있으므로, Monorepo의 별도 패키지로 분리하는 편이 낫습니다.
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;추가 권장사항
섹션 제목: “추가 권장사항”- 데이터베이스 쿼리는
sharedlayer의dbsegment에 정의하고, 상위 layer에서 가져와 사용합니다. - 쿼리의 캐싱과 재검증(revalidate) 로직은 쿼리 정의와 같은 위치에 둡니다.