주요 콘텐츠로 건너뛰기

Usage with Next.js

FSD는 Next.js의 App Router와 Pages Router 양쪽에 적용할 수 있습니다.
다만 Next.js가 요구하는 app, pages 폴더는 FSD 구조와 분리해 두어야 합니다.

App Router

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
├── 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의 페이지 파일을 가져와 연결합니다.

app/example/page.tsx
export { ExamplePage as default, metadata } from '@/pages/example';

Middleware

middleware를 사용한다면 Next.js의 app, pages 폴더와 함께 프로젝트 루트에 둡니다.

Instrumentation

instrumentation.js는 애플리케이션의 성능과 동작을 모니터링하는 파일입니다. middleware.js와 같은 이유로, 이 파일도 프로젝트 루트에 둡니다.

Pages Router

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의 페이지 파일을 가져와 연결합니다.

pages/example/index.tsx
export { Example as default } from '@/pages/example';

Custom _app 컴포넌트

Custom App 컴포넌트는 src/app/_app 또는 src/app/custom-app에 둘 수 있습니다. 루트의 pages/_app.tsx는 이 컴포넌트를 가져와 연결합니다.

src/app/custom-app/custom-app.tsx
import type { AppProps } from 'next/app';

export const MyApp = ({ Component, pageProps }: AppProps) => {
return (
<>
<p>My Custom App component</p>
<Component { ...pageProps } />
</>
);
};
pages/_app.tsx
export { App as default } from '@/app/custom-app';

Route Handlers (API routes)

Route Handlers는 app layer의 api-routes segment에 둡니다. 루트의 라우트 파일은 여기서 가져와 연결합니다.

FSD는 프론트엔드 애플리케이션 구조를 위한 방법론입니다. API 코드가 많다면 같은 구조 안에 함께 두는 방식은 구조 파악을 어렵게 만들 수 있으므로, Monorepo의 별도 패키지로 분리하는 편이 낫습니다.

src/app/api-routes/get-example-data.ts
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',
});
}
};
app/api/example/route.ts
export { getExampleData as GET } from '@/app/api-routes';

추가 권장사항

  • 데이터베이스 쿼리는 shared layer의 db segment에 정의하고, 상위 layer에서 가져와 사용합니다.
  • 쿼리의 캐싱과 재검증(revalidate) 로직은 쿼리 정의와 같은 위치에 둡니다.

참고 자료