diff --git a/.github/ISSUE_TEMPLATE/issue.md b/.github/ISSUE_TEMPLATE/issue.md index 42fcde6..27f254b 100644 --- a/.github/ISSUE_TEMPLATE/issue.md +++ b/.github/ISSUE_TEMPLATE/issue.md @@ -1,9 +1,9 @@ --- name: Acci Issue about: Acci 프론트엔드 이슈를 작성합니다 -labels: 'bug, enhancement, question, task' -title: '✨ feat : 이슈 제목' -assignees: '' +labels: "bug, enhancement, question, task" +title: "✨ feat : 이슈 제목" +assignees: "" --- ## ✅ TODO @@ -31,7 +31,7 @@ assignees: '' ## 🔗 참고 -- https://acci-ai.vercel.app +- https://acci-ai.site
diff --git a/FSD_PATHS.md b/FSD_PATHS.md index 3f8f09e..8c3b992 100644 --- a/FSD_PATHS.md +++ b/FSD_PATHS.md @@ -2,21 +2,106 @@ 교통사고 영상 분석 플랫폼을 위한 FSD(Feature-Sliced Design) 아키텍처 절대경로 설정 가이드입니다. +> ✅ **FSD 공식 권장 구조 적용 완료** +> 이 프로젝트는 FSD 공식 문서의 권장사항을 따릅니다: +> +> - 루트 `app/`: NextJS App Router (라우팅만) +> - `src/app/`: FSD app layer (providers, config만) +> - `src/pages/`: FSD pages layer (페이지 컴포넌트) + +## 📁 프로젝트 구조 + +``` +Acci-FrontEnd/ +├── app/ # NextJS App Router 폴더 (라우팅 진입점) +│ ├── page.tsx # 라우트 페이지들 +│ ├── layout.tsx # @/app/providers를 사용 +│ ├── globals.css # 전역 스타일 +│ ├── admin/ +│ │ └── page.tsx +│ ├── auth/ +│ │ └── page.tsx +│ ├── analyze/ +│ │ ├── page.tsx +│ │ ├── upload/ +│ │ ├── loading/ +│ │ └── result/ +│ ├── repair-estimate/ +│ ├── my-page/ +│ ├── policies/ +│ └── oauth2/ +├── pages/ # NextJS Pages Router 호환성 폴더 (빈 폴더) +│ └── README.md # ⚠️ 라우팅 파일 추가 금지 +└── src/ + ├── app/ # FSD app layer (providers, config만) + │ └── providers.tsx # 전역 providers (ReactQuery 등) + ├── pages/ # FSD pages layer (페이지 컴포넌트) + │ ├── home/ + │ ├── admin/ + │ ├── analyze/ + │ ├── repair-estimate/ + │ ├── my-page/ + │ ├── policies/ + │ └── auth/ + ├── widgets/ # FSD widgets layer + │ ├── header/ + │ ├── footer/ + │ ├── home/ + │ └── ... + ├── features/ # FSD features layer + │ ├── auth/ + │ ├── analyze/ + │ └── repair-estimate/ + ├── entities/ # FSD entities layer + │ ├── user/ + │ ├── analysis/ + │ ├── repair-estimate/ + │ └── vehicle/ + └── shared/ # FSD shared layer + ├── api/ + ├── ui/ + ├── lib/ + ├── store/ + └── icons/ +``` + +### ⚠️ 중요: 루트 `pages/` 폴더에 대해 + +Next.js는 `app/`와 `pages/`가 같은 레벨에 있어야 합니다. +루트 `pages/` 폴더는 **빈 폴더로 유지**하며, Next.js Pages Router 호환성을 위해서만 존재합니다. + +- **라우팅 파일은 추가하지 마세요**: 모든 라우팅은 `app/`에서 관리 +- **FSD pages layer와 혼동하지 마세요**: 실제 페이지 컴포넌트는 `src/pages/`에 작성 + ## 📁 설정된 절대경로 ```typescript // tsconfig.json "paths": { - "@/*": ["./src/*"], // 전체 src 폴더 - "@/app/*": ["./src/app/*"], // App Router - "@/pages/*": ["./src/pages/*"], // 페이지 레이어 - "@/widgets/*": ["./src/widgets/*"], // 위젯 레이어 - "@/features/*": ["./src/features/*"], // 기능 레이어 - "@/entities/*": ["./src/entities/*"], // 엔티티 레이어 - "@/shared/*": ["./src/shared/*"] // 공유 레이어 + "@/*": ["./src/*"], // 전체 src 폴더 + "@/app/*": ["./src/app/*"], // FSD app layer (providers, global config) + "@/pages/*": ["./src/pages/*"], // 📌 FSD pages layer (페이지 컴포넌트) + "@/widgets/*": ["./src/widgets/*"], // 위젯 레이어 + "@/features/*": ["./src/features/*"], // 기능 레이어 + "@/entities/*": ["./src/entities/*"], // 엔티티 레이어 + "@/shared/*": ["./src/shared/*"] // 공유 레이어 } ``` +### 📌 중요: 레이어별 역할 + +- **루트 `app/` 폴더**: NextJS App Router (라우팅 진입점만 담당) + - `page.tsx`, `layout.tsx`, `loading.tsx` 등 Next.js 라우팅 규칙 파일들 + - FSD pages layer의 컴포넌트를 import하여 사용 +- **`@/app/*` (src/app/)**: FSD app layer (providers, global config만) + - `providers.tsx`: 전역 providers (ReactQuery, Theme 등) + - 전역 설정 파일들 + - **라우팅 파일은 포함하지 않음** +- **`@/pages/*` (src/pages/)**: FSD pages layer + - 실제 페이지 컴포넌트 구현 + - widgets, features, entities, shared import 가능 + - **라우팅 폴더가 아님** (컴포넌트 저장소) + ## 🎯 사용 예시 ### 1. Shared 레이어에서 다른 Shared 레이어로 @@ -42,22 +127,54 @@ import { Container } from "@/shared/ui/container"; import { cn } from "@/shared/lib/utils"; ``` -### 4. Pages에서 Widgets와 Features 사용 +### 4. Pages (FSD)에서 Widgets와 Features 사용 ```typescript -// src/pages/home/ui/home-page.tsx +// src/pages/home/HomePage.tsx (FSD pages layer) import { Header } from "@/widgets/header"; import { Footer } from "@/widgets/footer"; -import { LoginForm } from "@/features/auth"; +import { HeroSection } from "@/widgets/home"; import { Button } from "@/shared/ui/button"; ``` -### 5. App에서 Pages 사용 +### 5. App Router에서 Pages 사용 ✅ ```typescript -// src/app/page.tsx -import { HomePage } from "@/pages/home"; -import { Button } from "@/shared/ui/button"; +// app/page.tsx (NextJS App Router) +import HomePage from "@/pages/home/HomePage"; +import { getUserInfo } from "@/entities/user/api/get-user-info"; + +export default async function Page() { + const initialUserInfo = await getUserInfo(); + return ; +} +``` + +```typescript +// app/layout.tsx (루트 레이아웃) +import { Providers } from "@/app/providers"; +import "./globals.css"; + +export default function RootLayout({ children }) { + return ( + + + {children} + + + ); +} +``` + +```typescript +// src/app/providers.tsx (FSD app layer) +"use client"; + +import { ReactQueryProvider } from "@/shared/lib/react-query"; + +export function Providers({ children }) { + return {children}; +} ``` ## 🚫 금지된 import 패턴 @@ -72,26 +189,57 @@ import { HomePage } from "@/pages/home"; // ❌ import { LoginForm } from "@/features/auth"; // ❌ // features에서 pages import (금지) -import { HomePage } from "@/pages/home"; // ❌ +import { SomePage } from "@/pages/home"; // ❌ + +// Pages에서 App Router 직접 접근 (금지) +import { useRouter } from "next/navigation"; // ❌ (Pages 컴포넌트는 "use client"을 지양) ``` ### ✅ 올바른 예시 ```typescript -// 상위 레이어에서 하위 레이어 import (허용) +// FSD 의존성 규칙 준수 (상위 → 하위) import { Button } from "@/shared/ui/button"; // ✅ import { User } from "@/entities/user"; // ✅ import { LoginForm } from "@/features/auth"; // ✅ +import HomePage from "@/pages/home/HomePage"; // ✅ (App Router에서만) ``` ## 📋 FSD 레이어 의존성 규칙 -1. **app** → pages, widgets, features, entities, shared -2. **pages** → widgets, features, entities, shared -3. **widgets** → features, entities, shared -4. **features** → entities, shared -5. **entities** → shared -6. **shared** → (다른 레이어 import 금지) +``` +app/ (루트 - NextJS App Router) + ↓ import +@/app (FSD app layer - providers) + ↓ import +@/pages (FSD pages layer) + ↓ import +@/widgets + @/features + ↓ import +@/entities + ↓ import +@/shared +``` + +### 의존성 사슬 (상위 → 하위만 가능) + +1. **app/** (루트 - App Router) → @/app, @/pages, @/widgets, @/features, @/entities, @/shared + - NextJS 라우팅 진입점 + - Server/Client Components 구분 +2. **@/app** (FSD app layer) → @/shared만 + - 전역 Providers (ReactQuery, Theme 등) + - Global configuration + - **다른 FSD 레이어 import 금지** +3. **@/pages** (FSD pages layer) → @/widgets, @/features, @/entities, @/shared + - 페이지 컴포넌트 (화면 구성) +4. **@/widgets** → @/features, @/entities, @/shared + - 기능 조합 (헤더, 푸터 등) +5. **@/features** → @/entities, @/shared + - 비즈니스 로직 (인증, 분석 등) +6. **@/entities** → @/shared + - 데이터 모델 (User, Analysis 등) +7. **@/shared** → 다른 레이어 불가 + - 기본 UI, 유틸, 상수 ## 🔧 추가 설정 @@ -100,9 +248,111 @@ import { LoginForm } from "@/features/auth"; // ✅ ```typescript const nextConfig: NextConfig = { experimental: { - typedRoutes: true, // 타입 안전한 라우팅 + optimizePackageImports: ["@/shared/ui", "@/shared/icons"], + }, + images: { + formats: ["image/avif", "image/webp"], }, }; ``` +### TypeScript 경로 설정 (tsconfig.json) + +```json +{ + "compilerOptions": { + "paths": { + "@/*": ["./src/*"], + "@/app/*": ["./src/app/*"], + "@/pages/*": ["./src/pages/*"], + "@/widgets/*": ["./src/widgets/*"], + "@/features/*": ["./src/features/*"], + "@/entities/*": ["./src/entities/*"], + "@/shared/*": ["./src/shared/*"] + } + } +} +``` + +## 📌 App Router + FSD 적용 시 주의사항 + +### 1. 폴더 역할 구분 + +- **루트 `app/`**: NextJS App Router (라우팅만) + - `page.tsx`, `layout.tsx`, `loading.tsx`, `error.tsx` 등 + - FSD pages layer 컴포넌트를 import +- **`src/app/`**: FSD app layer (providers, config만) + - `providers.tsx`: 전역 providers + - 설정 파일들 + - **라우팅 파일 금지** +- **`src/pages/`**: FSD pages layer (페이지 컴포넌트) + - 실제 페이지 UI 구현 + - **라우팅 폴더 아님** + +### 2. Server/Client Components 분리 + +```typescript +// src/pages/home/HomePage.tsx +// Server Component 기본 +import { getUserInfo } from "@/entities/user/api"; + +export default function HomePage({ initialUserInfo }) { + return
{/* ... */}
; +} +``` + +```typescript +// src/features/auth/ui/login-form.tsx +"use client"; // Client Component 명시 + +import { useState } from "react"; + +export function LoginForm() { + const [email, setEmail] = useState(""); + return
{/* ... */}
; +} +``` + +### 3. Import 자동완성 + +`@/pages`, `@/widgets` 등의 경로 alias를 사용하면 IDE 자동완성이 정확하게 작동합니다. + +### 4. FSD app layer 사용 예시 + +```typescript +// src/app/providers.tsx +"use client"; + +import { ReactQueryProvider } from "@/shared/lib/react-query"; +import { ThemeProvider } from "@/shared/ui/theme-provider"; + +export function Providers({ children }) { + return ( + + + {children} + + + ); +} +``` + +```typescript +// app/layout.tsx +import { Providers } from "@/app/providers"; +import "./globals.css"; + +export default function RootLayout({ children }) { + return ( + + + {children} + + + ); +} +``` + +--- + 이제 FSD 아키텍처에 맞는 절대경로를 사용하여 깔끔하고 일관된 import 구조를 유지할 수 있습니다! diff --git a/README.md b/README.md index c93638e..d85c1e2 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ## 🔗 Acci 바로가기 -https://acci-ai.vercel.app/ +https://acci-ai.site/
@@ -73,25 +73,57 @@ ## 📁 프로젝트 구조 (FSD) ``` -src/ -├── app/ # Next.js App Router -│ ├── layout.tsx -│ ├── page.tsx -│ └── globals.css -├── pages/ # 페이지 레이어 -│ └── home/ -├── widgets/ # 위젯 레이어 -│ ├── header/ -│ └── footer/ -├── features/ # 기능 레이어 -│ └── auth/ -├── entities/ # 엔티티 레이어 -│ └── user/ -└── shared/ # 공유 레이어 - ├── ui/ # 공통 UI 컴포넌트 - ├── lib/ # 유틸리티 함수 - ├── api/ # API 클라이언트 - └── config/ # 설정 파일 +Acci-FrontEnd/ +├── app/ # NextJS App Router 폴더 (라우팅 진입점) +│ ├── page.tsx # 라우트 페이지들 +│ ├── layout.tsx # @/app/providers를 사용 +│ ├── globals.css # 전역 스타일 +│ ├── admin/ +│ │ └── page.tsx +│ ├── auth/ +│ │ └── page.tsx +│ ├── analyze/ +│ │ ├── page.tsx +│ │ ├── upload/ +│ │ ├── loading/ +│ │ └── result/ +│ ├── repair-estimate/ +│ ├── my-page/ +│ ├── policies/ +│ └── oauth2/ +├── pages/ # NextJS Pages Router 호환성 폴더 (빈 폴더) +│ └── README.md # ⚠️ 라우팅 파일 추가 금지 +└── src/ + ├── app/ # FSD app layer (providers, config만) + │ └── providers.tsx # 전역 providers (ReactQuery 등) + ├── pages/ # FSD pages layer (페이지 컴포넌트) + │ ├── home/ + │ ├── admin/ + │ ├── analyze/ + │ ├── repair-estimate/ + │ ├── my-page/ + │ ├── policies/ + │ └── auth/ + ├── widgets/ # FSD widgets layer + │ ├── header/ + │ ├── footer/ + │ ├── home/ + │ └── ... + ├── features/ # FSD features layer + │ ├── auth/ + │ ├── analyze/ + │ └── repair-estimate/ + ├── entities/ # FSD entities layer + │ ├── user/ + │ ├── analysis/ + │ ├── repair-estimate/ + │ └── vehicle/ + └── shared/ # FSD shared layer + ├── api/ + ├── ui/ + ├── lib/ + ├── store/ + └── icons/ ```
diff --git a/src/app/admin/page.tsx b/app/admin/page.tsx similarity index 100% rename from src/app/admin/page.tsx rename to app/admin/page.tsx diff --git a/src/app/analyze/loading/page.tsx b/app/analyze/loading/page.tsx similarity index 100% rename from src/app/analyze/loading/page.tsx rename to app/analyze/loading/page.tsx diff --git a/src/app/analyze/page.tsx b/app/analyze/page.tsx similarity index 100% rename from src/app/analyze/page.tsx rename to app/analyze/page.tsx diff --git a/src/app/analyze/result/[id]/page.tsx b/app/analyze/result/[id]/page.tsx similarity index 100% rename from src/app/analyze/result/[id]/page.tsx rename to app/analyze/result/[id]/page.tsx diff --git a/src/app/analyze/upload/page.tsx b/app/analyze/upload/page.tsx similarity index 100% rename from src/app/analyze/upload/page.tsx rename to app/analyze/upload/page.tsx diff --git a/src/app/auth/page.tsx b/app/auth/page.tsx similarity index 100% rename from src/app/auth/page.tsx rename to app/auth/page.tsx diff --git a/src/app/favicon.ico b/app/favicon.ico similarity index 100% rename from src/app/favicon.ico rename to app/favicon.ico diff --git a/src/app/globals.css b/app/globals.css similarity index 100% rename from src/app/globals.css rename to app/globals.css diff --git a/src/app/layout.tsx b/app/layout.tsx similarity index 61% rename from src/app/layout.tsx rename to app/layout.tsx index ef4afbe..ac6a00d 100644 --- a/src/app/layout.tsx +++ b/app/layout.tsx @@ -1,58 +1,30 @@ -import { ReactQueryProvider } from "@/shared/lib/react-query"; +import { Providers } from "@/app/providers"; import type { Metadata, Viewport } from "next"; import localFont from "next/font/local"; import { Geist_Mono } from "next/font/google"; import "./globals.css"; +// 실제 사용하는 폰트 weight만 로드하여 초기 로딩 속도 개선 const pretendard = localFont({ variable: "--font-pretendard", display: "swap", + preload: true, // 폰트 프리로드 활성화 src: [ { - path: "../../public/fonts/pretendard/Pretendard-Thin.woff2", - weight: "100", - style: "normal", - }, - { - path: "../../public/fonts/pretendard/Pretendard-ExtraLight.woff2", - weight: "200", - style: "normal", - }, - { - path: "../../public/fonts/pretendard/Pretendard-Light.woff2", - weight: "300", - style: "normal", - }, - { - path: "../../public/fonts/pretendard/Pretendard-Regular.woff2", + path: "../public/fonts/pretendard/Pretendard-Regular.woff2", weight: "400", style: "normal", }, { - path: "../../public/fonts/pretendard/Pretendard-Medium.woff2", + path: "../public/fonts/pretendard/Pretendard-Medium.woff2", weight: "500", style: "normal", }, { - path: "../../public/fonts/pretendard/Pretendard-SemiBold.woff2", + path: "../public/fonts/pretendard/Pretendard-SemiBold.woff2", weight: "600", style: "normal", }, - { - path: "../../public/fonts/pretendard/Pretendard-Bold.woff2", - weight: "700", - style: "normal", - }, - { - path: "../../public/fonts/pretendard/Pretendard-ExtraBold.woff2", - weight: "800", - style: "normal", - }, - { - path: "../../public/fonts/pretendard/Pretendard-Black.woff2", - weight: "900", - style: "normal", - }, ], }); @@ -95,8 +67,13 @@ export default function RootLayout({ }>) { return ( + + {/* DNS Prefetch 및 Preconnect로 외부 리소스 로딩 최적화 */} + + + - {children} + {children} ); diff --git a/src/app/my-page/[id]/page.tsx b/app/my-page/[id]/page.tsx similarity index 100% rename from src/app/my-page/[id]/page.tsx rename to app/my-page/[id]/page.tsx diff --git a/src/app/my-page/analysis/page.tsx b/app/my-page/analysis/page.tsx similarity index 100% rename from src/app/my-page/analysis/page.tsx rename to app/my-page/analysis/page.tsx diff --git a/src/app/my-page/page.tsx b/app/my-page/page.tsx similarity index 100% rename from src/app/my-page/page.tsx rename to app/my-page/page.tsx diff --git a/src/app/my-page/repair-estimates/page.tsx b/app/my-page/repair-estimates/page.tsx similarity index 100% rename from src/app/my-page/repair-estimates/page.tsx rename to app/my-page/repair-estimates/page.tsx diff --git a/src/app/not-found.tsx b/app/not-found.tsx similarity index 100% rename from src/app/not-found.tsx rename to app/not-found.tsx diff --git a/src/app/oauth2/redirect/page.tsx b/app/oauth2/redirect/page.tsx similarity index 100% rename from src/app/oauth2/redirect/page.tsx rename to app/oauth2/redirect/page.tsx diff --git a/src/app/page.tsx b/app/page.tsx similarity index 100% rename from src/app/page.tsx rename to app/page.tsx diff --git a/src/app/policies/cookie-policy/page.tsx b/app/policies/cookie-policy/page.tsx similarity index 100% rename from src/app/policies/cookie-policy/page.tsx rename to app/policies/cookie-policy/page.tsx diff --git a/src/app/policies/page.tsx b/app/policies/page.tsx similarity index 100% rename from src/app/policies/page.tsx rename to app/policies/page.tsx diff --git a/src/app/policies/privacy-policy/page.tsx b/app/policies/privacy-policy/page.tsx similarity index 100% rename from src/app/policies/privacy-policy/page.tsx rename to app/policies/privacy-policy/page.tsx diff --git a/src/app/policies/terms-of-service/page.tsx b/app/policies/terms-of-service/page.tsx similarity index 100% rename from src/app/policies/terms-of-service/page.tsx rename to app/policies/terms-of-service/page.tsx diff --git a/src/app/repair-estimate/page.tsx b/app/repair-estimate/page.tsx similarity index 100% rename from src/app/repair-estimate/page.tsx rename to app/repair-estimate/page.tsx diff --git a/src/app/repair-estimate/result/[id]/page.tsx b/app/repair-estimate/result/[id]/page.tsx similarity index 100% rename from src/app/repair-estimate/result/[id]/page.tsx rename to app/repair-estimate/result/[id]/page.tsx diff --git a/next.config.ts b/next.config.ts index d972477..a9db9b6 100644 --- a/next.config.ts +++ b/next.config.ts @@ -4,6 +4,8 @@ const nextConfig: NextConfig = { /* config options here */ // FSD 아키텍처를 위한 절대경로 설정 typedRoutes: true, + + // 이미지 최적화 설정 images: { remotePatterns: [ { @@ -11,6 +13,18 @@ const nextConfig: NextConfig = { hostname: "acci-s3.s3.ap-northeast-2.amazonaws.com", }, ], + formats: ["image/avif", "image/webp"], // 최신 포맷 우선 사용 + deviceSizes: [640, 750, 828, 1080, 1200, 1920], // 반응형 이미지 크기 + minimumCacheTTL: 60, // 이미지 캐시 시간 (초) + }, + + // 성능 최적화 + compress: true, // gzip 압축 활성화 + poweredByHeader: false, // X-Powered-By 헤더 제거 (보안) + + // 실험적 기능 (성능 개선) + experimental: { + optimizePackageImports: ["@/shared/ui", "@/shared/icons"], // 패키지 import 최적화 }, }; diff --git a/pages/README.md b/pages/README.md new file mode 100644 index 0000000..f0527dc --- /dev/null +++ b/pages/README.md @@ -0,0 +1,15 @@ +# Pages Router Compatibility Folder + +⚠️ **이 폴더는 Next.js Pages Router 호환성을 위한 빈 폴더입니다.** + +## 용도 + +- Next.js는 `app/`와 `pages/`가 같은 레벨에 있어야 합니다 +- 루트에 `app/` (App Router)가 있고, `src/pages/` (FSD pages layer)가 있어서 +- Next.js가 인식할 수 있도록 루트에 빈 `pages/` 폴더를 생성했습니다 + +## 주의사항 + +- **이 폴더에는 라우팅 파일을 추가하지 마세요** +- 모든 라우팅은 루트 `app/` 폴더에서 관리됩니다 +- 실제 페이지 컴포넌트는 `src/pages/` (FSD pages layer)에 작성됩니다 diff --git a/public/images/balance-car.png b/public/images/balance-car.png deleted file mode 100644 index 68f9a33..0000000 Binary files a/public/images/balance-car.png and /dev/null differ diff --git a/public/images/balance-car.webp b/public/images/balance-car.webp new file mode 100644 index 0000000..3051b87 Binary files /dev/null and b/public/images/balance-car.webp differ diff --git a/src/app/providers.tsx b/src/app/providers.tsx new file mode 100644 index 0000000..e798eb9 --- /dev/null +++ b/src/app/providers.tsx @@ -0,0 +1,12 @@ +"use client"; + +import { ReactQueryProvider } from "@/shared/lib/react-query"; +import { ReactNode } from "react"; + +/** + * FSD app layer - 전역 providers + * App Router의 layout에서 사용하는 providers 모음 + */ +export function Providers({ children }: { children: ReactNode }) { + return {children}; +} diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx index fe8298a..3681db3 100644 --- a/src/pages/home/HomePage.tsx +++ b/src/pages/home/HomePage.tsx @@ -1,7 +1,21 @@ import type { UserInfo } from "@/entities/user/model/user-info"; import { Header } from "@/widgets/header/Header"; import { Footer } from "@/widgets/footer/Footer"; -import { HeroSection, FeaturesSection, EstimateSection, ReviewsSection, CtaSection } from "@/widgets/home"; +import { HeroSection, FeaturesSection } from "@/widgets/home"; +import dynamic from "next/dynamic"; + +// 스크롤 후 보이는 섹션들은 동적 로딩으로 초기 번들 크기 최적화 +const EstimateSection = dynamic(() => import("@/widgets/home").then((mod) => ({ default: mod.EstimateSection })), { + loading: () =>
, // 레이아웃 시프트 방지 +}); + +const ReviewsSection = dynamic(() => import("@/widgets/home").then((mod) => ({ default: mod.ReviewsSection })), { + loading: () =>
, +}); + +const CtaSection = dynamic(() => import("@/widgets/home").then((mod) => ({ default: mod.CtaSection })), { + loading: () =>
, +}); type HomePageProps = { initialUserInfo?: UserInfo | null; diff --git a/src/widgets/header/Header.tsx b/src/widgets/header/Header.tsx index 582ee2a..40ce1d5 100644 --- a/src/widgets/header/Header.tsx +++ b/src/widgets/header/Header.tsx @@ -35,7 +35,6 @@ export function Header({ initialUserInfo = null }: HeaderProps) { } }, [initialUserInfo, setUser]); - // 모바일 메뉴가 열렸을 때 body 스크롤 방지 useEffect(() => { if (mobileMenuOpen) { diff --git a/src/widgets/home/HeroSection.tsx b/src/widgets/home/HeroSection.tsx index b3cc0ae..5f52a45 100644 --- a/src/widgets/home/HeroSection.tsx +++ b/src/widgets/home/HeroSection.tsx @@ -23,7 +23,16 @@ export function HeroSection() {
- 과실비율 균형 + 과실비율 균형