Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1b36415
feat: Next.js 앱 라우터 골격 및 초기 마이그레이션 적용
sebeeeen Mar 28, 2026
da991be
chore: Next.js 전환 구조 정리 및 Vite 제거
sebeeeen Apr 2, 2026
1824e5f
chore: Next.js SSG 구조 정리 및 Firebase Hosting export 전환
sebeeeen Apr 4, 2026
d71e713
[Docs] Next.js 작업 방식 및 버전 문서 정리
sebeeeen Apr 4, 2026
148746d
Docs: Readme 내부 불필요한 변경사항 삭제
sebeeeen Apr 4, 2026
2ef8e3c
Docs: 마이그레이션 문서 삭제
sebeeeen Apr 4, 2026
7e2d829
Fix: Firebase env 및 modal/storybook 안정성 개선
sebeeeen Apr 4, 2026
fe17c7b
Fix: Storybook 실행 오류 해결
sebeeeen Apr 4, 2026
39f5faa
feat: Next.js 앱 라우터 골격 및 초기 마이그레이션 적용
sebeeeen Mar 28, 2026
f50b076
chore: Next.js 전환 구조 정리 및 Vite 제거
sebeeeen Apr 2, 2026
a271f77
chore: Next.js 전환 구조 정리 및 Vite 제거
sebeeeen Apr 2, 2026
9d35a04
chore: Next.js SSG 구조 정리 및 Firebase Hosting export 전환
sebeeeen Apr 4, 2026
8d62aff
[Docs] Next.js 작업 방식 및 버전 문서 정리
sebeeeen Apr 4, 2026
4046d06
Docs: Readme 내부 불필요한 변경사항 삭제
sebeeeen Apr 4, 2026
b87739f
Docs: 마이그레이션 문서 삭제
sebeeeen Apr 4, 2026
dcf0fb6
Fix: Firebase env 및 modal/storybook 안정성 개선
sebeeeen Apr 4, 2026
caf1413
Fix: Storybook 실행 오류 해결
sebeeeen Apr 4, 2026
d8fc370
Docs: version.md 업데이트
sebeeeen Apr 4, 2026
c0c0b6c
Fix: 코드 품질 및 Next.js 패턴 개선
sebeeeen Apr 5, 2026
b239e45
Chore: 에이전트 설정 파일 git 추적 제거
sebeeeen Apr 5, 2026
4183d3f
Merge: origin/feat/next-app-router 머지 및 충돌 해결
sebeeeen Apr 5, 2026
c6f8104
Chore: out/ 빌드 아티팩트 git 추적 제거
sebeeeen Apr 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pnpm-debug.log*
lerna-debug.log*

node_modules
.next
dist
dist-ssr
*.local
Expand Down
4 changes: 2 additions & 2 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { StorybookConfig } from "@storybook/react-vite";
import type { StorybookConfig } from "@storybook/nextjs";

const config: StorybookConfig = {
stories: ["../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
addons: ["@chromatic-com/storybook", "@storybook/addon-a11y", "@storybook/addon-docs"],
framework: "@storybook/react-vite",
framework: "@storybook/nextjs",
};
export default config;
2 changes: 1 addition & 1 deletion .storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Preview } from "@storybook/react-vite";
import type { Preview } from "@storybook/nextjs";
import "../src/index.css";

const preview: Preview = {
Expand Down
7 changes: 0 additions & 7 deletions .storybook/vitest.setup.ts

This file was deleted.

20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,8 @@

![React](https://img.shields.io/badge/React-61DAFB?style=for-the-badge&logo=react&logoColor=000)
![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=fff)
![Vite](https://img.shields.io/badge/Vite-646CFF?style=for-the-badge&logo=vite&logoColor=fff)
![Next.js](https://img.shields.io/badge/Next.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=fff)
![Storybook](https://img.shields.io/badge/Storybook-FF4785?style=for-the-badge&logo=storybook&logoColor=fff)
![TanStack Router](https://img.shields.io/badge/TanStack_Router-000000?style=for-the-badge)
![React Query](https://img.shields.io/badge/React_Query-FF4154?style=for-the-badge&logo=reactquery&logoColor=fff)
![Zustand](https://img.shields.io/badge/Zustand-000000?style=for-the-badge)
![TailwindCSS](https://img.shields.io/badge/TailwindCSS-06B6D4?style=for-the-badge&logo=tailwindcss&logoColor=fff)

### Backend
Expand All @@ -54,7 +51,7 @@

```bash
pnpm install
pnpm dev # 개발 서버
pnpm dev # Next.js 개발 서버
pnpm storybook # Storybook (포트 6006)
pnpm build # 프로덕션 빌드
pnpm lint # 린트 검사
Expand All @@ -64,6 +61,17 @@ pnpm lint # 린트 검사

---

## 현재 구조 메모

- 앱 실행 기준은 `Next.js App Router`입니다.
- `src/app`가 라우팅 엔트리이며, 공개 SEO 페이지와 인증 기반 내부 페이지를 분리하는 방향으로 전환을 진행 중입니다.
- Vite 직접 의존성과 레거시 앱 엔트리(`src/main.tsx`, `index.html`, `vite.config.ts`)는 제거했습니다.
- Storybook은 `@storybook/nextjs` 기준으로 전환했습니다.
- `public/index.html`은 현재 Firebase Hosting rewrite 호환을 위해 임시 유지 중입니다.
- 구조 설계 문서는 [docs/seo-nextjs-migration-plan.md](./docs/seo-nextjs-migration-plan.md)에서 관리합니다.

---

## 개발 가이드

프로젝트 구조, 컨벤션, 워크플로우는 **[CONTRIBUTING.md](./CONTRIBUTING.md)** 를 참고하세요.
프로젝트 구조, 컨벤션, 워크플로우는 **[docs/CONTRIBUTING.md](./docs/CONTRIBUTING.md)** 를 참고하세요.
120 changes: 79 additions & 41 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
# 의존성 설치
pnpm install

# 개발 서버 실행
# 개발 서버 실행 (Next.js)
pnpm dev

# Storybook 실행 (포트 6006)
Expand All @@ -47,15 +47,15 @@ Firebase 연동을 위해 `.env` 파일 설정이 필요합니다.

> `.env` 파일은 절대 GitHub에 커밋하지 마세요. `.gitignore`에 등록되어 있습니다.

환경 변수 값은 **팀 노션**을 참고해서 프로젝트 루트에 `.env` 파일을 생성하세요.
환경 변수 값은 **팀 노션**을 참고해서 프로젝트 루트에 `.env.local` 파일을 생성하세요.

```
VITE_FIREBASE_API_KEY=...
VITE_FIREBASE_AUTH_DOMAIN=...
VITE_FIREBASE_PROJECT_ID=...
VITE_FIREBASE_STORAGE_BUCKET=...
VITE_FIREBASE_MESSAGING_SENDER_ID=...
VITE_FIREBASE_APP_ID=...
NEXT_PUBLIC_FIREBASE_API_KEY=...
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=...
NEXT_PUBLIC_FIREBASE_PROJECT_ID=...
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=...
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=...
NEXT_PUBLIC_FIREBASE_APP_ID=...
```

---
Expand All @@ -67,13 +67,15 @@ aim-frontend/
├─ .storybook/ # Storybook 설정 (main.ts, preview.ts)
├─ public/
├─ src/
│ ├─ app/ # 애플리케이션 초기화
│ │ ├─ providers/ # Context providers
│ │ ├─ router/ # 라우팅 설정
│ │ ├─ index.tsx # App 컴포넌트
│ │ └─ index.css # 전역 스타일
│ ├─ app/ # Next.js App Router 엔트리
│ │ ├─ (auth)/ # 인증 관련 route group
│ │ │ └─ login/
│ │ │ └─ page.tsx
│ │ ├─ layout.tsx # 루트 레이아웃
│ │ ├─ page.tsx # 기본 라우트
│ │ └─ globals.css # 전역 스타일 진입점
│ │
│ ├─ pages/ # 페이지 레이어
│ ├─ views/ # 화면 레이어 (기존 pages 레이어 대체)
│ ├─ widgets/ # 위젯 레이어 (큰 UI 블록)
│ ├─ features/ # 기능 레이어
│ ├─ entities/ # 엔티티 레이어
Expand All @@ -93,11 +95,22 @@ aim-frontend/
├─ firestore.rules
├─ storage.rules
├─ eslint.config.js
├─ next.config.ts
├─ prettier.config.cjs
├─ tailwind.config.js
├─ public/index.html # Firebase Hosting rewrite 호환용 fallback 파일
└─ tsconfig.json
```

### 현재 실행 기준

- 앱 실행과 빌드는 `Next.js`를 기준으로 한다.
- 라우팅 엔트리는 `src/app`이며, 새 페이지는 `src/app/**/page.tsx`에 추가한다.
- 현재 인증 라우트는 `src/app/(auth)` route group 기준으로 관리한다.
- `src/main.tsx`, `index.html`, `vite.config.ts`, `src/firebase.js`는 제거되었다.
- Storybook은 `@storybook/nextjs` 기반으로 동작한다.
- `public/index.html`은 현재 Firebase Hosting rewrite 호환 때문에 임시 유지 중이다.

---

## 아키텍처
Expand All @@ -107,10 +120,11 @@ aim-frontend/
레이어 의존 방향은 **단방향**입니다. 하위 레이어는 상위 레이어를 import할 수 없습니다.

```
app → pages → widgets → features → entities → shared
app → views → widgets → features → entities → shared
```

- `@/` alias를 사용해 절대 경로로 import
- Next.js App Router와 충돌하지 않도록 화면 레이어는 `src/views`를 사용
- 각 슬라이스는 `index.ts`(public API)를 통해서만 export

```ts
Expand All @@ -124,11 +138,11 @@ import { AuthFeature } from '@/features/auth'

### 인프라 구조

현재는 Vite 기반 SPA이며, Next.js App Router 기반 SSR 전환이 검토 중입니다.
현재 앱 실행 기준은 Next.js App Router이며, Vite 기반 앱 엔트리는 제거되었다. 다만 Firebase Hosting은 아직 정적 `public` 기준 설정을 사용 중이라 fallback 파일 정리가 별도로 남아 있다.

```
브라우저
└─ Firebase Hosting (정적 파일 서빙)
└─ Next.js 앱
└─ Spring 백엔드 API 호출 (Oracle Cloud Run)
└─ MySQL (Oracle Server)

Expand Down Expand Up @@ -161,6 +175,16 @@ git checkout -b fix/header-layout

포맷팅되지 않은 코드는 커밋이 불가합니다.

### 3-1. 구조 변경 시 체크리스트

Next.js 전환 작업이나 구조 변경이 포함되면 아래 항목을 함께 확인합니다.

- 새 라우트가 `src/app` 기준으로 추가되었는가
- SEO 대상 페이지인지, CSR 유지 페이지인지 분류했는가
- `use client`가 꼭 필요한 곳에만 선언되었는가
- `NEXT_PUBLIC_*` 기준으로 환경 변수 사용이 정리되었는가
- `docs/VERSIONS.md`, 관련 가이드 문서가 함께 갱신되었는가

### 4. PR 생성

`dev` 브랜치로 PR을 올립니다. PR 템플릿에 맞춰 작성하세요.
Expand Down Expand Up @@ -212,10 +236,9 @@ enum ButtonVariant { Primary = "primary" }
```ts
// 1. 외부 라이브러리
import { useState } from 'react'
import { useQuery } from '@tanstack/react-query'

// 2. 내부 alias (FSD 레이어 순서)
import { apiClient } from '@/shared/api'
import { auth } from '@/shared/config/firebase'
import { UserEntity } from '@/entities/user'
import { AuthFeature } from '@/features/auth'

Expand Down Expand Up @@ -297,42 +320,57 @@ export const Loading: Story = {

| 용도 | 도구 |
|---|---|
| 서버 상태 (API 데이터) | **React Query** (`@tanstack/react-query`) |
| 클라이언트 전역 상태 | **Zustand** |
| 라우팅 | **TanStack Router** |
| 라우팅 | **Next.js App Router** |
| 서버 데이터 패칭 | **Next.js Server Component / fetch** |
| 폼 상태 관리 | **react-hook-form** |
| 입력값 검증 | **zod** |
| 클라이언트 전역 상태 | **zustand** |
| 단순 로컬 상호작용 상태 | **React state** (`useState`, `useReducer`) |

```ts
// React Query — 서버 데이터 패칭
const { data, isLoading } = useQuery({
queryKey: ['portfolios'],
queryFn: () => apiClient.get('/portfolios'),
})

// Zustand — 클라이언트 전역 상태
const useAuthStore = create<AuthStore>((set) => ({
user: null,
setUser: (user) => set({ user }),
}))
// Server Component — 서버 데이터 패칭
async function PortfolioPage() {
const data = await fetch("https://api/portfolios").then((response) => response.json())
return <PortfolioList data={data} />
}

// Client Component — 상호작용 상태
"use client"

function LikeButton() {
const [liked, setLiked] = useState(false)
return <button onClick={() => setLiked(true)}>좋아요</button>
}
```

권장 원칙:

- 폼은 `react-hook-form + zod` 조합을 기본으로 사용한다.
- 여러 컴포넌트에서 공유되는 클라이언트 상태는 `zustand`를 사용한다.
- 서버 데이터는 가능한 한 Server Component 또는 서버 측 fetch로 가져온다.
- 단순한 토글/탭/입력 표시 상태는 `useState`로 시작한다.

---

## API 호출 가이드

모든 API 요청은 `src/shared/api`의 Axios 인스턴스를 사용합니다.
현재 저장소에는 공통 API 클라이언트 레이어가 아직 없다. API 연동이 시작되면 `src/shared/api` 또는 `src/shared/lib/api` 하위에 공통 fetch/HTTP 래퍼를 두고 일관되게 사용하는 것을 기본 정책으로 한다.

권장 원칙:

- 자동 Authorization 헤더 삽입
- 에러 처리 인터셉터
- Response 타입 안전성 확보
- 서버 컴포넌트에서 우선적으로 데이터 패칭
- 인증 헤더/토큰 주입 로직 중앙화
- 에러 처리와 응답 파싱 로직 공통화
- 브라우저 전용 상호작용만 Client Component에서 처리

### Next.js 도입 시 (검토 중)
### Next.js 기준 권장 방식

Next.js App Router 전환 후에는 보안을 위해 **서버 컴포넌트에서 API를 호출**합니다. 클라이언트에서 백엔드를 직접 호출하는 것을 최소화하고, 인터랙션이 필요한 컴포넌트에만 `"use client"`를 선언합니다.
보안을 위해 **서버 컴포넌트에서 API를 호출**하는 방식을 우선합니다. 클라이언트에서 백엔드를 직접 호출하는 경우는 사용자 입력, 이벤트 처리, 즉시 반응이 필요한 상호작용으로 제한합니다.

```tsx
// 서버 컴포넌트 — 데이터 패칭
async function PortfolioPage() {
const data = await fetch('https://api/portfolios').then(r => r.json())
const data = await fetch("https://api/portfolios").then((response) => response.json())
return <PortfolioList data={data} />
}

Expand Down Expand Up @@ -379,4 +417,4 @@ pnpm remove [패키지명]

### `main` 머지 시

- Firebase Hosting Live 자동 배포
- Firebase Hosting Live 자동 배포
Loading