[as] refactor: 폴더 구조 변경(src)

This commit is contained in:
2026-02-26 15:14:19 +09:00
parent 240311e540
commit bd53c17fec
41 changed files with 112 additions and 103 deletions
+30 -36
View File
@@ -13,46 +13,40 @@ Next.js 애플리케이션 with FSD(Feature Sliced Design) 아키텍처
- **HTTP 클라이언트**: Axios
- **날짜 처리**: Day.js
## 프로젝트 구조 (FSD)
## 프로젝트 구조 (FSD + Next.js App Router)
이 프로젝트는 **Feature-Sliced Design v2.1**을 따르며, 계층적 아키텍처로 구성되어 있습니다.
이 프로젝트는 **Feature-Sliced Design v2.1**과 **FSD 공식 Next.js 가이드**에 맞춰 구성되어 있습니다. Next.js `app/`은 라우트만 담당하고, 실제 레이어는 `src/` 아래에 있습니다.
```
as/
├── app/ # App 레이어 (Next.js App Router)
│ ├── (routes)/ # 라우트 구조
│ ├── layout.tsx # 루트 레이아웃
│ ├── page.tsx # 홈 페이지
│ ├── globals.css # 글로벌 스타일
│ └── providers.tsx # 전역 providers
├── app/ # Next.js App Router (루트) — 라우트·레이아웃 진입점만
│ ├── layout.tsx # 루트 레이아웃 (src/app에서 providers, styles import)
│ └── (with-base-layout)/
│ ├── layout.tsx # BaseLayout 래퍼 (src/app/layouts에서 import)
│ └── page.tsx # re-export: export { MainPage as default } from '@/pages/home'
│
├── widgets/ # Widgets 레이어
│ └── [widget-name]/ # 큰 독립적 UI 블록 (여러 페이지에서 재사용)
│ ├── ui/
│ ├── model/
│ └── index.ts
├── pages/ # 빈 폴더 + README (Next.js가 src/pages를 Pages Router로 쓰지 않도록)
│ └── README.md
│
├── features/ # Features 레이어
│ └── [feature-name]/ # 사용자 상호작용 기능 (여러 페이지에서 재사용)
│ ├── ui/
│ ├── api/
│ ├── model/
│ └── index.ts
│
├── entities/ # Entities 레이어
│ └── [entity-name]/ # 비즈니스 엔티티 (User, Product 등)
│ ├── model/
│ ├── api/
│ ├── ui/
│ └── index.ts
│
└── shared/ # Shared 레이어
├── ui/ # UI Kit (비즈니스 로직 없는 공통 컴포넌트)
├── api/ # API 클라이언트 설정
├── lib/ # 내부 라이브러리 (dates, colors 등)
├── config/ # 환경 설정, feature flags
├── types/ # 공통 타입 정의
└── routes/ # 라우트 상수
└── src/ # FSD 레이어 (실제 코드)
├── app/ # App 레이어 (슬라이스 없음, 세그먼트만)
│ ├── providers/ # 전역 Providers (QueryClient 등)
│ ├── styles/ # 글로벌 스타일 (globals.css)
│ └── layouts/ # 공통 레이아웃 컴포넌트 (BaseLayoutWrapper)
│
├── pages/ # Pages 레이어 (페이지 슬라이스)
│ └── home/
│ ├── ui/
│ └── index.ts # Public API
│
├── widgets/ # Widgets 레이어
├── features/ # Features 레이어
├── entities/ # Entities 레이어
└── shared/ # Shared 레이어
├── ui/
├── api/
├── lib/
└── config/
```
### 계층 구조 이해하기
@@ -86,10 +80,10 @@ shared/ ↓ 가장 낮은 책임, 의존성 없음
- **특징**: 슬라이스가 없고 세그먼트로만 구성
- **의존성**: 모든 하위 레이어 사용 가능
### 2. Pages (app 디렉토리)
### 2. Pages (src/pages)
- **목적**: 웹사이트나 애플리케이션을 구성하는 페이지/화면
- **내용**: 페이지 UI, 로딩 상태, 에러 바운더리, 데이터 fetching
- **특징**: 한 페이지당 한 슬라이스 (유사한 페이지는 그룹화 가능)
- **특징**: 한 페이지당 한 슬라이스 (유사한 페이지는 그룹화 가능). Next.js `app/`의 route 파일에서 re-export
- **의존성**: `widgets`, `features`, `entities`, `shared` 사용 가능
> 💡 **FSD v2.1 권장사항**: "pages-first" 접근 방식을 따라, 재사용되지 않는 큰 UI 블록과 비즈니스 로직은 페이지 슬라이스에 유지하세요.
+7 -29
View File
@@ -1,31 +1,9 @@
"use client";
import { BaseLayoutWrapper } from "@/app/layouts/base-layout";
import { useCallback } from "react";
import { usePathname, useRouter } from "next/navigation";
import { useQueryClient } from "@tanstack/react-query";
import { BaseLayout } from "@common/components";
import type { MenuItem } from "@/entities/menu";
import { PageLayout } from "@/shared/ui/page-layout";
export default function WithBaseLayout({ children }: { children: React.ReactNode }) {
const router = useRouter();
const pathname = usePathname();
const queryClient = useQueryClient();
const handleMenuClick = useCallback(
(item: MenuItem) => {
router.push(item.urlPath);
},
[router],
);
return (
<BaseLayout
queryClient={queryClient}
currentPath={pathname ?? ""}
handleMenuClick={handleMenuClick}
>
<PageLayout>{children}</PageLayout>
</BaseLayout>
);
interface WithBaseLayoutProps {
children: React.ReactNode;
}
export default function WithBaseLayout({ children }: WithBaseLayoutProps) {
return <BaseLayoutWrapper>{children}</BaseLayoutWrapper>;
}
+1 -9
View File
@@ -1,9 +1 @@
"use client";
export default function MainPage() {
return (
<div className="flex h-full flex-col items-center justify-center py-10">
<h1 className="mb-4 text-3xl font-bold">Home</h1>
</div>
);
}
export { MainPage as default } from "@/pages/home";
+2 -2
View File
@@ -1,10 +1,10 @@
import type { Metadata } from "next";
import { cookies } from "next/headers";
import { Noto_Sans_KR } from "next/font/google";
import { Providers } from "./providers";
import { Providers } from "@/app/providers";
import { setCookie } from "@/shared/lib";
import { SITE_ID } from "@/shared/config";
import "./globals.css";
import "@/app/styles/globals.css";
const notoSansKR = Noto_Sans_KR({
subsets: ["latin"],
+1
View File
@@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
+9
View File
@@ -0,0 +1,9 @@
# 이 폴더는 비워 둡니다 (FSD + Next.js App Router)
Next.js가 `src/pages`를 **Pages Router**용 `pages` 디렉터리로 인식하지 않도록,
프로젝트 루트에 **빈 `pages` 폴더**를 두는 것이 FSD 공식 가이드 권장사항입니다.
- 실제 페이지(FSD Pages 레이어)는 **`src/pages`**에 있습니다.
- Next.js 라우트는 **`app/`** 에서 `src/pages`를 re-export 하는 방식으로 연결됩니다.
[Feature-Sliced Design - Usage with Next.js (App Router)](https://feature-sliced.design/docs/guides/tech/with-nextjs)
+35
View File
@@ -0,0 +1,35 @@
"use client";
import { useCallback } from "react";
import { usePathname, useRouter } from "next/navigation";
import { useQueryClient } from "@tanstack/react-query";
import { BaseLayout } from "@common/components";
import type { MenuItem } from "@/entities/menu";
import { PageLayout } from "@/shared/ui/page-layout";
interface BaseLayoutWrapperProps {
children: React.ReactNode;
}
export function BaseLayoutWrapper({ children }: BaseLayoutWrapperProps) {
const router = useRouter();
const pathname = usePathname();
const queryClient = useQueryClient();
const handleMenuClick = useCallback(
(item: MenuItem) => {
router.push(item.urlPath);
},
[router],
);
return (
<BaseLayout
queryClient={queryClient}
currentPath={pathname ?? ""}
handleMenuClick={handleMenuClick}
>
<PageLayout>{children}</PageLayout>
</BaseLayout>
);
}
@@ -1,15 +1,15 @@
@import "@common/assets/styles/base.css";
@import "@common/assets/styles/diveicon.css";
@import "@common/components/index.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
html,
body {
color: var(--foreground);
background: var(--background);
font-family: var(--font-noto-sans-kr), sans-serif;
font-size: 10px;
}
@import "@common/assets/styles/base.css";
@import "@common/assets/styles/diveicon.css";
@import "@common/components/index.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
html,
body {
color: var(--foreground);
background: var(--background);
font-family: var(--font-noto-sans-kr), sans-serif;
font-size: 10px;
}
+1
View File
@@ -0,0 +1 @@
export { MainPage } from "./ui/main-page";
+9
View File
@@ -0,0 +1,9 @@
"use client";
export function MainPage() {
return (
<div className="flex h-full flex-col items-center justify-center py-10">
<h1 className="mb-4 text-3xl font-bold">Home</h1>
</div>
);
}
+2 -12
View File
@@ -13,19 +13,9 @@
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"plugins": [{ "name": "next" }],
"paths": {
"@/*": ["./*"],
"@/app/*": ["./app/*"],
"@/shared/*": ["./shared/*"],
"@/entities/*": ["./entities/*"],
"@/features/*": ["./features/*"],
"@/widgets/*": ["./widgets/*"],
"@/pages/*": ["./pages/*"],
"@/*": ["./src/*"],
"@common/components": ["../../packages/common/components/dist/"],
"@common/network": ["../../packages/common/network/"]
}