[as] refactor: 폴더 구조 변경(src)
This commit is contained in:
+30
-36
@@ -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 블록과 비즈니스 로직은 페이지 슬라이스에 유지하세요.
|
||||
|
||||
@@ -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 +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";
|
||||
|
||||
@@ -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"],
|
||||
|
||||
Vendored
+1
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { MainPage } from "./ui/main-page";
|
||||
@@ -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
@@ -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/"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user