콘텐츠로 이동

Usage with Next.js

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

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

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

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

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

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

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

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는 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) 로직은 쿼리 정의와 같은 위치에 둡니다.