[docker build] 기본 구조 점검

This commit is contained in:
2025-06-26 18:02:28 +09:00
parent 8dc5721010
commit f1fe697b9c
6 changed files with 1750 additions and 0 deletions
+232
View File
@@ -0,0 +1,232 @@
# Next.js App Router의 라우트 그룹(Route Groups) 구현 분석
## 개요
Next.js의 App Router에서는 괄호로 묶인 디렉토리(`(with-base-layout)`, `(without-base-layout)`)를 라우트 그룹으로 처리하여 URL 경로에 영향을 주지 않으면서 서로 다른 레이아웃을 적용할 수 있게 합니다. 이 문서에서는 프로젝트에서 라우트 그룹이 어떻게 구현되고 사용되는지 분석합니다.
## 라우트 그룹 구조 분석
### 1. 디렉토리 구조
프로젝트에서는 두 가지 주요 라우트 그룹이 사용되고 있습니다:
```
/apps/cs/app/
├── (with-base-layout)/
│ ├── banner/
│ ├── bbs/
│ ├── code/
│ ├── contents/
│ ├── dashboard/
│ ├── ...
│ ├── layout.tsx
│ └── loading.tsx
├── (without-base-layout)/
│ ├── user-survey-article/
│ └── user-survey-result/
└── layout.tsx
```
### 2. 라우트 그룹별 레이아웃 구현
#### 루트 레이아웃 (`/apps/cs/app/layout.tsx`)
```tsx
import type { Metadata } from "next";
import { App } from "@/app";
export const metadata: Metadata = {
title: "유역하수도 통합 정보플랫폼",
};
export default App;
```
#### App 컴포넌트 (`/apps/cs/src/app/index.tsx`)
```tsx
import "./styles/base.css";
import "./styles/components.css";
import "@fontsource-variable/noto-sans-kr";
import { AppProvider } from "./providers";
import { cookies } from "next/headers";
const App = async ({ children }: Readonly<{ children: React.ReactNode }>) => {
const theme = (await cookies()).get("site-theme-key");
return (
<html lang="ko" data-theme={theme?.value}>
<body>
<AppProvider>{children}</AppProvider>
</body>
</html>
);
};
export { App };
```
#### (with-base-layout) 레이아웃 (`/apps/cs/app/(with-base-layout)/layout.tsx`)
```tsx
"use client";
import { usePathname, useRouter } from "next/navigation";
import { useQueryClient } from "@tanstack/react-query";
import { BaseLayout } from "@common/components";
import type { MenuItem } from "@/entities/menu";
import { useMenuStore } from "@/shared/lib";
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 setCurrentMenuInfo = useMenuStore((state) => state.setCurrentMenuInfo);
const handleMenuClick = (item: MenuItem) => {
setCurrentMenuInfo(item);
router.push(item.urlPath);
};
return (
<BaseLayout
queryClient={queryClient}
currentPath={pathname ?? ""}
handleMenuClick={handleMenuClick}
>
<PageLayout>{children}</PageLayout>
</BaseLayout>
);
}
```
#### PageLayout 컴포넌트 (`/apps/cs/src/shared/ui/page-layout.tsx`)
```tsx
import { PageTitle } from "@common/components";
import { useMenuStore } from "../lib/use-menu-store";
function PageLayout({ children }: { children: React.ReactNode }) {
const currentMenuInfo = useMenuStore((state) => state.currentMenuInfo);
const menuPaths = currentMenuInfo?.menuPath
?.split(">")
.map((path) => path.trim());
return (
<div className="h-auto min-h-full bg-container pb-2">
<PageTitle
title={currentMenuInfo?.menuKornNm ?? ""}
menuPaths={menuPaths ?? []}
/>
{children}
</div>
);
}
```
## 라우트 그룹의 작동 원리
### 1. URL 경로에 영향을 주지 않는 그룹화
Next.js App Router에서 괄호로 묶인 디렉토리는 URL 경로에 포함되지 않습니다. 예를 들어:
- `/bbs/register` 경로는 `(with-base-layout)/bbs/register` 디렉토리에 매핑됩니다.
- `/user-survey-article/1` 경로는 `(without-base-layout)/user-survey-article/[srvySn]` 디렉토리에 매핑됩니다.
### 2. 레이아웃 분리 및 재사용
라우트 그룹을 사용하면 서로 다른 레이아웃을 가진 페이지들을 효율적으로 구성할 수 있습니다:
- `(with-base-layout)` 그룹: 메뉴, 헤더, 사이드바 등을 포함하는 `BaseLayout`과 `PageLayout`을 사용합니다.
- `(without-base-layout)` 그룹: 기본 레이아웃 없이 페이지 컴포넌트만 렌더링합니다.
### 3. 중첩 라우트 그룹
프로젝트에서는 중첩된 라우트 그룹도 사용되고 있습니다:
```
(with-base-layout)/bbs/(post)/bbsnormal/
```
여기서 `(post)`는 게시물 관련 페이지들을 그룹화하는 중첩된 라우트 그룹입니다.
## 라우트 그룹 사용 사례
### 1. 관리자 인터페이스 (`with-base-layout`)
관리자 인터페이스는 일관된 레이아웃과 네비게이션이 필요합니다:
```tsx
// (with-base-layout)/layout.tsx
return (
<BaseLayout
queryClient={queryClient}
currentPath={pathname ?? ""}
handleMenuClick={handleMenuClick}
>
<PageLayout>{children}</PageLayout>
</BaseLayout>
);
```
이 레이아웃은 다음과 같은 기능을 제공합니다:
- 메뉴 시스템을 통한 페이지 간 이동
- 현재 페이지 경로 표시
- 일관된 페이지 타이틀 및 구조
### 2. 독립적인 페이지 (`without-base-layout`)
설문 기사나 결과 페이지와 같은 독립적인 페이지는 기본 레이아웃 없이 콘텐츠에 집중합니다:
```tsx
// (without-base-layout)/user-survey-article/[srvySn]/page.tsx
export default async function UserSurveyArticlePage({
params,
}: {
params: Promise<{ srvySn: string }>;
}) {
const { srvySn } = await params;
return <UserSurveyArticle srvySn={srvySn} />;
}
```
이러한 페이지는:
- 외부 링크나 팝업으로 열리는 경우가 많음
- 독립적인 기능을 제공(예: 설문 제출 후 창 닫기)
- 메인 애플리케이션 UI와 분리된 경험 제공
## 두 라우트 그룹의 주요 차이점
### (with-base-layout)
- `BaseLayout` 컴포넌트를 포함하는 레이아웃을 사용합니다.
- 이 레이아웃은 메뉴, 헤더, 사이드바 등 기본 UI 요소를 포함합니다.
- `PageLayout` 컴포넌트로 감싸져 있어 일관된 페이지 구조를 제공합니다.
- 주로 관리자 페이지나 로그인 후 사용하는 내부 페이지에 사용됩니다.
- 다양한 관리 기능을 포함하는 여러 하위 디렉토리가 있습니다.
### (without-base-layout)
- 기본 레이아웃 없이 페이지 컴포넌트만 렌더링합니다.
- 메뉴, 헤더, 사이드바 등의 UI 요소가 없는 독립적인 페이지입니다.
- 주로 외부 사용자용 페이지나 팝업, 임베드 페이지에 사용됩니다.
- 현재 두 가지 특수 목적 페이지만 포함하고 있습니다.
## 기술적 이점
1. **코드 구성 개선**: 관련 페이지와 레이아웃을 논리적으로 그룹화할 수 있습니다.
2. **레이아웃 재사용**: 공통 레이아웃을 여러 페이지에서 재사용할 수 있습니다.
3. **URL 구조 단순화**: 그룹 이름이 URL에 포함되지 않아 깔끔한 URL 구조를 유지할 수 있습니다.
4. **유지보수성 향상**: 관련 기능을 함께 그룹화하여 코드 탐색과 유지보수가 용이합니다.
## 결론
Next.js App Router의 라우트 그룹 기능은 프로젝트에서 다양한 레이아웃 요구사항을 효율적으로 관리하는 데 중요한 역할을 합니다. `(with-base-layout)`과 `(without-base-layout)` 그룹을 통해 관리자 인터페이스와 독립적인 페이지를 명확하게 분리하면서도 코드 재사용성과 유지보수성을 높일 수 있습니다.
이 구조는 확장성이 뛰어나 새로운 페이지나 기능을 추가할 때 기존 패턴을 따르기 쉽고, 애플리케이션이 성장함에 따라 필요한 경우 추가 라우트 그룹을 도입할 수 있는 유연성을 제공합니다.
+650
View File
@@ -0,0 +1,650 @@
# 게시물 댓글(Comment) 관리 프로그램 아키텍처 문서
## 개요
이 문서는 게시물 댓글(Comment) 관리 프로그램의 아키텍처와 소스 흐름에 대한 상세 설명입니다. 댓글 관리 기능은 게시물에 대한 댓글을 등록, 조회, 수정, 삭제하는 기능을 제공하며, 로그인 및 비로그인 사용자 모두 이용할 수 있습니다.
## 기술 스택
- **프레임워크**: Next.js (App Router)
- **상태 관리**: React Query, Zustand(useUserStore, useAlertStore)
- **UI 컴포넌트**: @common/components
- **폼 관리**: React Hook Form, Zod
- **API 통신**: Axios
- **알림 처리**: useConfirm, usePrompt 커스텀 훅
## 디렉토리 구조
```
frontend/
├── apps/
│ └── cs/
│ ├── src/
│ │ ├── entities/
│ │ │ └── comment/
│ │ │ ├── api/
│ │ │ │ └── comment-service.ts # 댓글 API 호출 함수
│ │ │ └── model/
│ │ │ └── comment-interface.ts # 댓글 인터페이스 정의
│ │ └── widgets/
│ │ └── post/
│ │ └── ui/
│ │ └── post-comment.tsx # 댓글 UI 컴포넌트
```
## 데이터 모델
### 댓글(Comment) 인터페이스
```typescript
export interface Comment {
cmntEvl: number; // 댓글 평가 점수
pstNo: number; // 게시물 번호
cmntNo: number; // 댓글 번호
cmntCn: string; // 댓글 내용
wrtrNm: string; // 작성자명
regDt: string; // 등록일시
rgtr: string; // 등록자
mdfcnDt: string; // 수정일시
mdfr: string; // 수정자
delYn: string; // 삭제 여부
}
```
### 댓글 검색 파라미터(CommentParams) 인터페이스
```typescript
export interface CommentParams extends PageableParams {
keywordType?: string; // 검색 키워드 유형
keyword?: string; // 검색 키워드
pstNo: number; // 게시물 번호
delYn?: string; // 삭제 여부
}
```
### 댓글 폼 데이터(CommentFormData) 인터페이스
Zod 스키마를 사용하여 폼 데이터의 유효성 검증을 수행합니다.
```typescript
export const commentFormSchema = z.object({
pstNo: z.number(), // 게시물 번호
cmntCn: z.string(), // 댓글 내용
wrtrNm: z.string().nullish(), // 작성자명 (선택적)
cmntPswd: z.string(), // 댓글 비밀번호
cmntEvl: z.number().nullish(), // 댓글 평가 점수 (선택적)
});
export type CommentFormData = z.infer<typeof commentFormSchema>;
```
### 댓글 수정 폼 데이터(CommentUpdateFormData) 인터페이스
```typescript
export const commentUpdateFormSchema = commentFormSchema.omit({
pstNo: true, // 수정 시에는 게시물 번호가 필요 없음
});
export type CommentUpdateFormData = z.infer<typeof commentUpdateFormSchema>;
```
## API 기능
댓글 관리 API는 다음과 같은 기능을 제공합니다:
### 1. 댓글 목록 조회
```typescript
getComments: async (params: CommentParams): Promise<CommentList> => {
const response = await client.get<CommentList>(
`${ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS}`,
{ params },
);
return response.data;
}
```
- **설명**: 특정 게시물의 댓글 목록을 페이지네이션과 함께 조회합니다.
- **파라미터**: `CommentParams` (게시물 번호, 검색 조건)
- **반환값**: 페이지네이션된 댓글 목록 (`CommentList`)
### 2. 댓글 등록
```typescript
createComment: async (data: CommentFormData): Promise<Comment> => {
const validatedData = commentFormSchema.parse(data);
const response = await client.post<Comment>(
`${ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS}`,
validatedData,
);
return response.data;
}
```
- **설명**: 새로운 댓글을 등록합니다.
- **파라미터**: `CommentFormData` (댓글 등록 데이터)
- **반환값**: 등록된 댓글 정보 (`Comment`)
### 3. 댓글 수정
```typescript
updateComment: async (
bbsId: number,
pstNo: number,
cmntNo: number,
data: CommentUpdateFormData,
): Promise<Comment> => {
const validatedData = commentUpdateFormSchema.parse(data);
const response = await client.put<Comment>(
`${ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS}/${bbsId}/${pstNo}/${cmntNo}`,
validatedData,
);
return response.data;
}
```
- **설명**: 기존 댓글 정보를 수정합니다.
- **파라미터**: `bbsId` (게시판 ID), `pstNo` (게시물 번호), `cmntNo` (댓글 번호), `CommentUpdateFormData` (수정할 댓글 데이터)
- **반환값**: 수정된 댓글 정보 (`Comment`)
### 4. 댓글 삭제
```typescript
deleteComment: async (
bbsId: number,
pstNo: number,
cmntNo: number,
cmntPswd: string,
): Promise<Comment> => {
const response = await client.delete<Comment>(
`${ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS}/${bbsId}/${pstNo}/${cmntNo}`,
{ params: { cmntPswd } },
);
return response.data;
}
```
- **설명**: 댓글을 삭제합니다.
- **파라미터**: `bbsId` (게시판 ID), `pstNo` (게시물 번호), `cmntNo` (댓글 번호), `cmntPswd` (댓글 비밀번호)
- **반환값**: 삭제된 댓글 정보 (`Comment`)
## 프로그램 흐름
### 1. 댓글 목록 조회 흐름
1. 게시물 상세 페이지에서 `PostComment` 컴포넌트 로드
2. React Query를 사용하여 `getComments` API 호출
```typescript
const { data: comments } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS, pstNo],
queryFn: () => CommentApi.getComments({ pstNo }),
enabled: !!pstNo,
});
```
3. 응답 데이터를 UI에 표시
```tsx
{comments?.content.map((comment) => {
if (comment.delYn === "Y") return null;
return (
<div className="flex justify-between gap-2 border-t border-gray-200 py-8" key={comment.cmntNo}>
{/* 댓글 내용 표시 */}
</div>
);
})}
```
### 2. 댓글 등록 흐름
1. 사용자가 댓글 내용 입력 및 등록 버튼 클릭
2. React Hook Form을 통해 폼 데이터 유효성 검증
```typescript
const form = useForm<CommentFormData>({
resolver: zodResolver(commentFormSchema),
values: {
pstNo,
cmntCn: "",
wrtrNm: userInfo?.userName ?? "",
cmntPswd: userInfo?.userUnqId ?? "",
},
});
```
3. 유효성 검증 통과 시 `onSubmit` 함수 호출
```typescript
const onSubmit = (data: CommentFormData) => {
createComment(data);
};
```
4. React Query의 `useMutation` 훅을 통해 `createComment` API 호출
```typescript
const { mutate: createComment } = useMutation({
mutationFn: (data: CommentFormData) => CommentApi.createComment(data),
onSuccess: () => {
alert("등록되었습니다.");
form.setValue("cmntCn", "");
queryClient.invalidateQueries({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS],
});
},
});
```
5. 성공 시 알림 표시 및 댓글 목록 새로고침
### 3. 댓글 수정 흐름
1. 사용자가 댓글 수정 버튼 클릭
2. 수정 모드 활성화 및 기존 댓글 내용 로드
```typescript
const handleEdit = (cmntNo: number, cmntCn: string) => {
setEditingCmntNo(cmntNo);
setEditValue(cmntCn);
};
```
3. 사용자가 댓글 내용 수정 후 저장 버튼 클릭
4. 확인 다이얼로그 표시 및 비밀번호 확인
```typescript
const handleUpdateComment = async (cmntNo: number, wrtrNm: string) => {
if (!(await confirm("수정하시겠습니까?"))) return;
const cmntPswd = await getCommentPassword();
updateComment({
cmntNo,
data: {
cmntCn: editValue,
wrtrNm,
cmntPswd,
},
});
};
```
5. React Query의 `useMutation` 훅을 통해 `updateComment` API 호출
6. 성공 시 알림 표시 및 댓글 목록 새로고침
### 4. 댓글 삭제 흐름
1. 사용자가 댓글 삭제 버튼 클릭
2. 확인 다이얼로그 표시 및 비밀번호 확인
```typescript
const handleDeleteComment = async (cmntNo: number) => {
if (!(await confirm("삭제하시겠습니까?"))) return;
const cmntPswd = await getCommentPassword();
deleteComment({ cmntNo, cmntPswd });
};
```
3. React Query의 `useMutation` 훅을 통해 `deleteComment` API 호출
4. 성공 시 알림 표시 및 댓글 목록 새로고침
## 주요 컴포넌트
### PostComment 컴포넌트
게시물 댓글을 표시하고 관리하는 컴포넌트입니다.
```tsx
function PostComment({ bbsId, pstNo }: { bbsId: number; pstNo: number }) {
// 상태 및 훅 설정
const { prompt } = usePrompt();
const { confirm } = useConfirm();
const queryClient = useQueryClient();
const { userInfo } = useUserStore((state) => state);
const { setMessage: alert } = useAlertStore((state) => state);
const [editingCmntNo, setEditingCmntNo] = useState<number | null>(null);
const [editValue, setEditValue] = useState<string>("");
// 댓글 목록 조회
const { data: comments } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS, pstNo],
queryFn: () => CommentApi.getComments({ pstNo }),
enabled: !!pstNo,
});
// 댓글 등록 뮤테이션
const { mutate: createComment } = useMutation({
mutationFn: (data: CommentFormData) => CommentApi.createComment(data),
onSuccess: () => {
alert("등록되었습니다.");
form.setValue("cmntCn", "");
queryClient.invalidateQueries({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS],
});
},
});
// 댓글 수정 뮤테이션
const { mutate: updateComment } = useMutation({
mutationFn: ({
cmntNo,
data,
}: {
cmntNo: number;
data: CommentUpdateFormData;
}) => CommentApi.updateComment(bbsId, pstNo, cmntNo, data),
onSuccess: () => {
alert("수정되었습니다.");
setEditingCmntNo(null);
setEditValue("");
queryClient.invalidateQueries({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS],
});
},
});
// 댓글 삭제 뮤테이션
const { mutate: deleteComment } = useMutation({
mutationFn: ({ cmntNo, cmntPswd }: { cmntNo: number; cmntPswd: string }) =>
CommentApi.deleteComment(bbsId, pstNo, cmntNo, cmntPswd),
onSuccess: () => {
alert("삭제되었습니다.");
queryClient.invalidateQueries({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS],
});
},
});
// 폼 설정
const form = useForm<CommentFormData>({
resolver: zodResolver(commentFormSchema),
values: {
pstNo,
cmntCn: "",
wrtrNm: userInfo?.userName ?? "",
cmntPswd: userInfo?.userUnqId ?? "",
},
});
// 폼 제출 핸들러
const onSubmit = (data: CommentFormData) => {
createComment(data);
};
// 비밀번호 획득 함수
const getCommentPassword = async () => {
let cmntPswd = "";
if (userInfo?.userUnqId) {
cmntPswd = userInfo?.userUnqId;
} else {
const { resolved, value } = await prompt("비밀번호를 입력해주세요.");
if (resolved) cmntPswd = value ?? "";
}
return cmntPswd;
};
// 댓글 수정 핸들러
const handleUpdateComment = async (cmntNo: number, wrtrNm: string) => {
if (!(await confirm("수정하시겠습니까?"))) return;
const cmntPswd = await getCommentPassword();
updateComment({
cmntNo,
data: {
cmntCn: editValue,
wrtrNm,
cmntPswd,
},
});
};
// 댓글 삭제 핸들러
const handleDeleteComment = async (cmntNo: number) => {
if (!(await confirm("삭제하시겠습니까?"))) return;
const cmntPswd = await getCommentPassword();
deleteComment({ cmntNo, cmntPswd });
};
// UI 렌더링
return (
<div className="card card-border !p-8">
{/* 댓글 등록 폼 */}
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* 댓글 내용 입력 필드 */}
<FormField
control={form.control}
name="cmntCn"
render={({ field }) => (
<FormItem>
<FormControl>
<Textarea
maxLength={1000}
className="h-[10rem] bg-background p-4 !text-[1.3rem] !leading-6"
placeholder="댓글을 입력해주세요."
{...field}
/>
</FormControl>
</FormItem>
)}
/>
<div className="mt-4 flex items-center justify-between">
<div className="flex gap-4">
{/* 비로그인 사용자를 위한 작성자명 입력 필드 */}
{!userInfo?.userName && (
<FormField
control={form.control}
name="wrtrNm"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
value={field.value ?? ""}
className="w-[16rem] bg-background !text-[1.3rem]"
placeholder="작성자"
/>
</FormControl>
</FormItem>
)}
/>
)}
{/* 비로그인 사용자를 위한 비밀번호 입력 필드 */}
{!userInfo?.userUnqId && (
<FormField
control={form.control}
name="cmntPswd"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
type="password"
value={field.value ?? ""}
className="w-[12rem] bg-background !text-[1.3rem]"
placeholder="비밀번호"
/>
</FormControl>
</FormItem>
)}
/>
)}
</div>
<Button type="submit">등록</Button>
</div>
</form>
</Form>
{/* 댓글 목록 */}
<div className="mt-8 flex flex-col">
{comments?.content.map((comment) => {
if (comment.delYn === "Y") return null;
return (
<div
className="flex justify-between gap-2 border-t border-gray-200 py-8"
key={comment.cmntNo}
>
{/* 댓글 정보 표시 */}
<div className="flex w-full gap-10">
<div className="flex w-[10rem] shrink-0 flex-col">
<span className="text-[1.4rem] font-bold text-label">
{comment.wrtrNm}
</span>
<span className="text-[1.2rem] text-label">
{dayjs(comment.regDt).format("YYYY.MM.DD")}
</span>
</div>
{/* 수정 모드일 때 */}
{editingCmntNo === comment.cmntNo ? (
<div className="flex flex-1 flex-col gap-2">
<Textarea
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
className="h-[10rem] bg-background p-4 !text-[1.3rem] !leading-6"
/>
<div className="flex justify-end gap-2">
<Button
onClick={() => {
setEditingCmntNo(null);
setEditValue("");
}}
>
취소
</Button>
<Button
onClick={() =>
handleUpdateComment(comment.cmntNo, comment.wrtrNm)
}
>
저장
</Button>
</div>
</div>
) : (
/* 일반 모드일 때 */
<div className="flex flex-1 flex-col">
<p className="whitespace-pre-wrap text-[1.4rem] text-label">
{comment.cmntCn}
</p>
</div>
)}
</div>
{/* 수정/삭제 버튼 */}
{editingCmntNo !== comment.cmntNo && (
<div className="flex gap-2">
<Button
variant="outline"
onClick={() => handleEdit(comment.cmntNo, comment.cmntCn)}
>
수정
</Button>
<Button
variant="outline"
color="red"
onClick={() => handleDeleteComment(comment.cmntNo)}
>
삭제
</Button>
</div>
)}
</div>
);
})}
</div>
</div>
);
}
```
## 로그인/비로그인 사용자 처리
### 로그인 사용자 처리
로그인 사용자의 경우 `useUserStore`에서 사용자 정보를 가져와 자동으로 설정합니다.
```typescript
const { userInfo } = useUserStore((state) => state);
const form = useForm<CommentFormData>({
resolver: zodResolver(commentFormSchema),
values: {
pstNo,
cmntCn: "",
wrtrNm: userInfo?.userName ?? "",
cmntPswd: userInfo?.userUnqId ?? "",
},
});
```
### 비로그인 사용자 처리
비로그인 사용자의 경우 작성자명과 비밀번호를 직접 입력받습니다.
```tsx
{!userInfo?.userName && (
<FormField
control={form.control}
name="wrtrNm"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
value={field.value ?? ""}
className="w-[16rem] bg-background !text-[1.3rem]"
placeholder="작성자"
/>
</FormControl>
</FormItem>
)}
/>
)}
{!userInfo?.userUnqId && (
<FormField
control={form.control}
name="cmntPswd"
render={({ field }) => (
<FormItem>
<FormControl>
<Input
{...field}
type="password"
value={field.value ?? ""}
className="w-[12rem] bg-background !text-[1.3rem]"
placeholder="비밀번호"
/>
</FormControl>
</FormItem>
)}
/>
)}
```
## 비밀번호 검증 로직
댓글 수정/삭제 시 비밀번호 검증을 위한 로직이 구현되어 있습니다.
```typescript
const getCommentPassword = async () => {
let cmntPswd = "";
if (userInfo?.userUnqId) {
// 로그인 사용자의 경우 사용자 ID를 비밀번호로 사용
cmntPswd = userInfo?.userUnqId;
} else {
// 비로그인 사용자의 경우 프롬프트를 통해 비밀번호 입력 요청
const { resolved, value } = await prompt("비밀번호를 입력해주세요.");
if (resolved) cmntPswd = value ?? "";
}
return cmntPswd;
};
```
## 아키텍처 특징
1. **계층 분리**: API, 모델, UI 컴포넌트가 명확히 분리되어 있습니다.
2. **타입 안전성**: TypeScript 인터페이스와 Zod 스키마를 통한 타입 검증을 제공합니다.
3. **상태 관리**: React Query를 통한 서버 상태 관리와 Zustand를 통한 클라이언트 상태 관리를 제공합니다.
4. **유효성 검증**: Zod 스키마를 사용하여 폼 데이터의 유효성을 검증합니다.
5. **사용자 경험**: 로그인/비로그인 사용자에 대한 차별화된 UI 제공합니다.
6. **비동기 처리**: React Query의 `useMutation`을 통한 효율적인 비동기 작업 처리를 제공합니다.
## 확장 가능성
1. **댓글 계층 구조**: 대댓글 기능 추가
2. **댓글 좋아요/싫어요**: 댓글 평가 기능 강화
3. **댓글 신고**: 부적절한 댓글 신고 기능 추가
4. **댓글 알림**: 댓글 등록 시 알림 기능 추가
5. **댓글 정렬**: 최신순, 인기순 등 다양한 정렬 옵션 제공
6. **댓글 검색**: 댓글 내용 검색 기능 추가
## 주의사항
1. 비로그인 사용자의 댓글 비밀번호는 클라이언트 측에서만 검증되므로 보안에 주의가 필요합니다.
2. 댓글 삭제 시 실제로 데이터베이스에서 삭제하지 않고 `delYn` 필드를 'Y'로 설정하는 소프트 삭제 방식을 사용합니다.
3. 댓글 내용에 HTML 태그나 스크립트가 포함될 경우 XSS 공격 가능성이 있으므로 서버 측에서 적절한 필터링이 필요합니다.
+480
View File
@@ -0,0 +1,480 @@
# 게시물(Post) 관리 프로그램 아키텍처 문서
## 개요
이 문서는 게시물(Post) 관리 프로그램의 아키텍처와 소스 흐름에 대한 상세 설명입니다. 게시물 관리 기능은 게시판에 게시물을 등록, 조회, 수정, 삭제하는 기능을 제공하며, 다양한 유형의 게시물(일반, 이미지, 동영상, 공지사항 등)을 지원합니다.
## 기술 스택
- **프레임워크**: Next.js (App Router)
- **상태 관리**: React Query, Zustand(useMenuStore, useUserStore)
- **UI 컴포넌트**: @common/components
- **테이블 관리**: TanStack Table
- **폼 관리**: React Hook Form, Zod
- **API 통신**: Axios
- **파일 업로드**: 커스텀 파일 업로더 컴포넌트
## 디렉토리 구조
```
frontend/
├── apps/
│ └── cs/
│ ├── app/
│ │ └── (with-base-layout)/
│ │ └── bbs/
│ │ └── (post)/ # 게시물 관련 페이지
│ ├── src/
│ │ ├── entities/
│ │ │ ├── post/
│ │ │ │ ├── api/
│ │ │ │ │ └── post-service.ts # 게시물 API 호출 함수
│ │ │ │ └── model/
│ │ │ │ └── post-interface.ts # 게시물 인터페이스 정의
│ │ │ └── posting/
│ │ │ ├── api/
│ │ │ │ └── posting-service.ts # 게시 관련 API 호출 함수
│ │ │ ├── model/
│ │ │ │ └── posting-interface.ts # 게시 관련 인터페이스 정의
│ │ │ └── ui/
│ │ │ └── posting-dialog.tsx # 게시 관련 다이얼로그 컴포넌트
│ │ ├── pages/
│ │ │ └── (post)/
│ │ │ ├── image/
│ │ │ │ └── ui/
│ │ │ │ └── image-post.tsx # 이미지 게시물 컴포넌트
│ │ │ ├── movie/
│ │ │ │ └── ui/
│ │ │ │ └── movie-post.tsx # 동영상 게시물 컴포넌트
│ │ │ ├── normal/
│ │ │ │ └── ui/
│ │ │ │ └── normal-post.tsx # 일반 게시물 컴포넌트
│ │ │ └── notice/
│ │ │ └── ui/
│ │ │ └── notice-post.tsx # 공지사항 게시물 컴포넌트
│ │ └── widgets/
│ │ └── post/
│ │ ├── model/
│ │ │ ├── post-table-columns.tsx # 게시물 테이블 컬럼 정의
│ │ │ └── grid-view-table-columns.tsx # 그리드 뷰 테이블 컬럼 정의
│ │ └── ui/
│ │ ├── post-action.tsx # 게시물 액션 컴포넌트
│ │ ├── post-comment.tsx # 게시물 댓글 컴포넌트
│ │ ├── post-form.tsx # 게시물 등록/수정 폼
│ │ ├── post-page.tsx # 게시물 목록 페이지
│ │ ├── post.tsx # 게시물 상세 컴포넌트
│ │ └── posting-period-selector.tsx # 게시 기간 선택기
```
## 데이터 모델
### 게시물(Post) 인터페이스
```typescript
export interface Post {
rowNo: number; // 행 번호
bbsId: number; // 게시판 ID
pstNo: number; // 게시물 번호
pstTtl: string; // 게시물 제목
pstCn: string; // 게시물 내용
inqNocs: number; // 조회수
rfrncUrl: string; // 참조 URL
popupYn: string; // 팝업 여부
upndFixYn: string; // 상단 고정 여부
prdPstgYn: string; // 기간 게시 여부
rootPath: string; // 루트 경로
pstPath: string; // 게시물 경로
ansPstn: number; // 답변 위치
wrtrNm: string; // 작성자명
wrtrCplc: string; // 작성자 소속
atflCd: string; // 첨부파일 코드
regDt: string; // 등록일시
rgtrNm: string; // 등록자명
cmntCnt: number; // 댓글 수
}
```
### 게시물 상세 정보(PostInfo) 인터페이스
```typescript
export interface PostInfo extends Post {
pstCn: string; // 게시물 내용
rgtr: string; // 등록자
mdfcnDt: string; // 수정일시
mdfr: string; // 수정자
}
```
### 최근 게시물(RecentPost) 인터페이스
```typescript
export interface RecentPost extends Post {
bbsNm: string; // 게시판명
bbsTypeCd: string; // 게시판 유형 코드
}
```
### 게시물 이동(MovePost) 인터페이스
```typescript
export interface MovePost {
fromBbsId: number; // 출발 게시판 ID
toBbsId?: number; // 도착 게시판 ID
pstNo: number; // 게시물 번호
}
```
### 게시물 삭제(DeletePost) 인터페이스
```typescript
export interface DeletePost {
bbsId: number; // 게시판 ID
pstNo: number; // 게시물 번호
delRsn?: string; // 삭제 사유
}
```
### 게시물 검색 파라미터(PostParams) 인터페이스
```typescript
export interface PostParams extends PageableParams {
keywordType?: string; // 검색 키워드 유형 (제목, 내용, 작성자 등)
keyword?: string; // 검색 키워드
popupYn?: string; // 팝업 여부
upndFixYn?: string; // 상단 고정 여부
prdPstgYn?: string; // 기간 게시 여부
siteId?: string; // 사이트 ID
bbsTypeCd?: string; // 게시판 유형 코드
}
```
### 최근 게시물 검색 파라미터(RecentPostParams) 인터페이스
```typescript
export interface RecentPostParams extends PageableParams {
keywordType?: string; // 검색 키워드 유형
keyword?: string; // 검색 키워드
bbsId?: number | null; // 게시판 ID
delYn?: string; // 삭제 여부
frDateYmd?: string; // 시작 날짜
toDateYmd?: string; // 종료 날짜
}
```
### 게시물 폼 데이터(PostFormData) 인터페이스
Zod 스키마를 사용하여 폼 데이터의 유효성 검증을 수행합니다.
```typescript
export const postFormSchema = z.object({
bbsId: z.number(), // 게시판 ID
pstTtl: z.string().nonempty(), // 게시물 제목
pstCn: z.string().nullish(), // 게시물 내용
rfrncUrl: z.string().nullish(), // 참조 URL
popupYn: z.string().nullish(), // 팝업 여부
popupYnEtc: z.array(postEtcSchema).nullish(), // 팝업 관련 추가 정보
upndFixYn: z.string().nullish(), // 상단 고정 여부
upndFixYnEtc: z.array(postEtcSchema).nullish(), // 상단 고정 관련 추가 정보
prdPstgYn: z.string().nullish(), // 기간 게시 여부
prdPstgYnEtc: z.array(postEtcSchema).nullish(), // 기간 게시 관련 추가 정보
pstPswd: z.string(), // 게시물 비밀번호
wrtrNm: z.string().nullish(), // 작성자명
wrtrCplc: z.string().nullish(), // 작성자 소속
atflCd: z.string().nullish(), // 첨부파일 코드
parentPstNo: z.number().nullish(), // 부모 게시물 번호
});
```
## API 기능
게시물 관리 API는 다음과 같은 기능을 제공합니다:
### 1. 게시물 목록 조회
```typescript
getPostList: async (bbsId: number, params: PostParams): Promise<PostList> => {
const response = await client.get<PostList>(
`${ENDPOINT.BOARD_SERVICE.POSTS}/${bbsId}`,
{ params },
);
return response.data;
}
```
- **설명**: 특정 게시판의 게시물 목록을 페이지네이션과 함께 조회합니다.
- **파라미터**: `bbsId` (게시판 ID), `PostParams` (검색 조건)
- **반환값**: 페이지네이션된 게시물 목록 (`PostList`)
### 2. 최근 게시물 목록 조회
```typescript
getRecentPostList: async (params?: RecentPostParams): Promise<RecentPostList> => {
const response = await client.get<RecentPostList>(
`${ENDPOINT.BOARD_SERVICE.POSTS}/recentPosts`,
{ params },
);
return response.data;
}
```
- **설명**: 최근 게시물 목록을 조회합니다.
- **파라미터**: `RecentPostParams` (검색 조건)
- **반환값**: 페이지네이션된 최근 게시물 목록 (`RecentPostList`)
### 3. 게시물 상세 조회
```typescript
getPostInfo: async (bbsId: number, pstNo: number): Promise<PostInfo> => {
const response = await client.get<PostInfo>(
`${ENDPOINT.BOARD_SERVICE.POSTS}/${bbsId}/${pstNo}`,
);
return response.data;
}
```
- **설명**: 특정 게시물의 상세 정보를 조회합니다.
- **파라미터**: `bbsId` (게시판 ID), `pstNo` (게시물 번호)
- **반환값**: 게시물 상세 정보 (`PostInfo`)
### 4. 게시물 생성
```typescript
createPost: async (data: PostFormData): Promise<Post> => {
const validatedData = postFormSchema.parse(data);
const response = await client.post<Post>(
`${ENDPOINT.BOARD_SERVICE.POSTS}`,
validatedData,
);
return response.data;
}
```
- **설명**: 새로운 게시물을 생성합니다.
- **파라미터**: `PostFormData` (게시물 생성 데이터)
- **반환값**: 생성된 게시물 정보 (`Post`)
### 5. 게시물 수정
```typescript
updatePost: async (bbsId: number, pstNo: number, data: PostUpdateFormData): Promise<Post> => {
const validatedData = postUpdateFormSchema.parse(data);
const response = await client.put<Post>(
`${ENDPOINT.BOARD_SERVICE.POSTS}/${bbsId}/${pstNo}`,
validatedData,
);
return response.data;
}
```
- **설명**: 기존 게시물 정보를 수정합니다.
- **파라미터**: `bbsId` (게시판 ID), `pstNo` (게시물 번호), `PostUpdateFormData` (수정할 게시물 데이터)
- **반환값**: 수정된 게시물 정보 (`Post`)
### 6. 게시물 삭제
```typescript
deletePost: async (bbsId: number, pstNo: number, delRsn?: string): Promise<void> => {
await client.delete(`${ENDPOINT.BOARD_SERVICE.POSTS}/${bbsId}/${pstNo}`, {
params: { delRsn },
});
}
```
- **설명**: 게시물을 삭제합니다.
- **파라미터**: `bbsId` (게시판 ID), `pstNo` (게시물 번호), `delRsn` (삭제 사유, 선택적)
- **반환값**: void
### 7. 게시물 소프트 삭제 (여러 게시물)
```typescript
softDeletePost: async (data: DeletePost[]): Promise<void> => {
await client.post(`${ENDPOINT.BOARD_SERVICE.POSTS}/softDeleteSels`, data);
}
```
- **설명**: 여러 게시물을 소프트 삭제합니다 (실제 삭제하지 않고 삭제 표시만 함).
- **파라미터**: `DeletePost[]` (삭제할 게시물 목록)
- **반환값**: void
### 8. 게시물 하드 삭제 (여러 게시물)
```typescript
hardDeletePost: async (data: DeletePost[]): Promise<void> => {
await client.post(`${ENDPOINT.BOARD_SERVICE.POSTS}/hardDeleteSels`, data);
}
```
- **설명**: 여러 게시물을 하드 삭제합니다 (실제로 데이터베이스에서 삭제).
- **파라미터**: `DeletePost[]` (삭제할 게시물 목록)
- **반환값**: void
### 9. 게시물 이동
```typescript
movePost: async (data: MovePost[]): Promise<void> => {
await client.post(`${ENDPOINT.BOARD_SERVICE.POSTS}/movePstByBbsId`, data);
}
```
- **설명**: 게시물을 다른 게시판으로 이동합니다.
- **파라미터**: `MovePost[]` (이동할 게시물 정보 목록)
- **반환값**: void
### 10. 게시물 복구
```typescript
restorePost: async (data: DeletePost[]): Promise<void> => {
await client.post(`${ENDPOINT.BOARD_SERVICE.POSTS}/restoreSels`, data);
}
```
- **설명**: 소프트 삭제된 게시물을 복구합니다.
- **파라미터**: `DeletePost[]` (복구할 게시물 목록)
- **반환값**: void
## 프로그램 흐름
### 1. 게시물 목록 조회 흐름
1. 사용자가 게시판 페이지에 접근
2. `useMenuStore`를 통해 현재 게시판 정보 로드
3. React Query를 사용하여 `getPostList` API 호출
4. 상단 고정 게시물이 있는 경우 별도로 조회하여 목록 상단에 표시
5. 검색 조건 변경 시 쿼리 파라미터 업데이트 및 API 재호출
6. 응답 데이터를 TanStack Table을 사용하여 테이블 형태로 표시
7. 게시판 유형에 따라 일반 테이블 또는 그리드 뷰로 표시
### 2. 게시물 상세 조회 흐름
1. 사용자가 게시물 목록에서 특정 게시물 클릭
2. 해당 게시물의 상세 페이지로 이동 (`/bbs/{bbsId}/{pstNo}`)
3. React Query를 사용하여 `getPostInfo` API 호출
4. 게시물 상세 정보 및 첨부파일 정보 로드
5. 게시판 유형에 따라 적절한 상세 보기 컴포넌트 렌더링
- 일반 게시물: `normal-post.tsx`
- 이미지 게시물: `image-post.tsx`
- 동영상 게시물: `movie-post.tsx`
- 공지사항: `notice-post.tsx`
### 3. 게시물 등록 흐름
1. 사용자가 게시물 등록 버튼 클릭
2. 게시물 등록 페이지로 이동 (`/bbs/{bbsId}/register`)
3. React Hook Form과 Zod를 사용하여 폼 데이터 관리 및 유효성 검증
4. 첨부파일 업로드 시 `AttachmentApi`를 통해 파일 업로드 및 `atflCd` 획득
5. 저장 버튼 클릭 시 확인 다이얼로그 표시 후 `createPost` API 호출
6. 성공 시 게시물 목록 페이지로 리다이렉트
### 4. 게시물 수정 흐름
1. 사용자가 게시물 상세 페이지에서 수정 버튼 클릭
2. 게시물 수정 페이지로 이동 (`/bbs/{bbsId}/{pstNo}/modify`)
3. `getPostInfo` API를 호출하여 기존 게시물 데이터 로드
4. 기존 첨부파일 정보 로드 및 표시
5. 사용자가 데이터 수정 후 저장 버튼 클릭
6. 확인 다이얼로그 표시 후 `updatePost` API 호출
7. 성공 시 게시물 상세 페이지로 리다이렉트
### 5. 게시물 삭제 흐름
1. 사용자가 게시물 상세 페이지에서 삭제 버튼 클릭 또는 목록에서 여러 게시물 선택 후 삭제 버튼 클릭
2. 삭제 사유 입력 다이얼로그 표시
3. 사용자가 삭제 사유 입력 후 확인
4. 단일 게시물 삭제 시 `deletePost` API 호출, 여러 게시물 삭제 시 `softDeletePost` 또는 `hardDeletePost` API 호출
5. 성공 시 게시물 목록 페이지로 리다이렉트 또는 목록 새로고침
## 주요 컴포넌트
### 1. PostPage 컴포넌트
게시물 목록을 조회하고 표시하는 페이지 컴포넌트입니다.
- **기능**:
- 게시물 검색 (제목, 내용, 작성자 등)
- 페이지네이션
- 페이지 크기 조정
- 게시물 목록 표시 (테이블 또는 그리드 뷰)
- 게시물 등록 페이지로 이동
- 게시물 상세 페이지로 이동 (행 클릭)
- 선택한 게시물 삭제
### 2. PostForm 컴포넌트
게시물 등록 및 수정에 사용되는 폼 컴포넌트입니다.
- **기능**:
- 게시물 정보 입력/수정 (제목, 내용, 작성자 등)
- 첨부파일 업로드 및 관리
- 참조 URL 입력 (게시판 설정에 따라)
- 팝업 게시 설정 (게시판 설정에 따라)
- 상단 고정 설정 (게시판 설정에 따라)
- 기간 게시 설정 (게시판 설정에 따라)
- 저장 확인 다이얼로그
- 취소 기능
### 3. Post 컴포넌트
게시물 상세 정보를 표시하는 컴포넌트입니다.
- **기능**:
- 게시물 상세 정보 표시
- 첨부파일 다운로드
- 수정/삭제 버튼
- 목록으로 돌아가기
- 댓글 표시 및 관리 (게시판 설정에 따라)
### 4. PostComment 컴포넌트
게시물 댓글을 표시하고 관리하는 컴포넌트입니다.
- **기능**:
- 댓글 목록 표시
- 댓글 등록
- 댓글 수정/삭제
- 댓글 페이지네이션
### 5. PostingPeriodSelector 컴포넌트
게시 기간을 선택하는 컴포넌트입니다.
- **기능**:
- 게시 기간 유형 선택
- 시작일/종료일 선택
- 게시 기간 설정
### 6. 게시물 유형별 컴포넌트
게시판 유형에 따라 다양한 게시물 컴포넌트를 제공합니다.
- **NormalPost**: 일반 텍스트 게시물 컴포넌트
- **ImagePost**: 이미지 게시물 컴포넌트 (이미지 갤러리 형태)
- **MoviePost**: 동영상 게시물 컴포넌트 (동영상 플레이어 포함)
- **NoticePost**: 공지사항 게시물 컴포넌트
## 아키텍처 특징
1. **계층 분리**: API, 모델, UI 컴포넌트가 명확히 분리되어 있습니다.
2. **타입 안전성**: TypeScript 인터페이스와 Zod 스키마를 통한 타입 검증을 제공합니다.
3. **컴포넌트 재사용**: 공통 컴포넌트를 활용하여 코드 중복을 최소화합니다.
4. **유효성 검증**: Zod 스키마를 사용하여 폼 데이터의 유효성을 검증합니다.
5. **상태 관리**: React Query를 통한 서버 상태 관리와 Zustand를 통한 클라이언트 상태 관리를 제공합니다.
6. **조건부 렌더링**: 게시판 설정에 따라 UI 요소를 조건부로 렌더링합니다.
7. **다양한 게시물 유형 지원**: 일반, 이미지, 동영상, 공지사항 등 다양한 게시물 유형을 지원합니다.
## 확장 가능성
1. **다국어 지원**: 게시물 내용의 다국어 지원 기능 추가
2. **게시물 버전 관리**: 게시물 수정 이력 관리 기능 추가
3. **게시물 예약 발행**: 특정 시간에 자동으로 게시되는 기능 추가
4. **게시물 통계**: 조회수, 좋아요 등 게시물 통계 기능 강화
5. **게시물 공유**: 소셜 미디어 공유 기능 추가
6. **게시물 알림**: 새 게시물 알림 기능 추가
7. **게시물 태그**: 게시물 태그 및 카테고리 기능 추가
## 주의사항
1. 게시물 삭제 시 연관된 첨부파일, 댓글 등의 처리에 주의가 필요합니다.
2. 게시물 이동 시 게시판 유형 간 호환성 문제가 발생할 수 있습니다.
3. 첨부파일 업로드 시 파일 크기 및 유형 제한을 준수해야 합니다.
4. 게시 기간 설정 시 시간대(timezone) 처리에 주의가 필요합니다.
5. 비로그인 사용자의 게시물 작성 시 비밀번호 관리에 주의가 필요합니다.
+96
View File
@@ -0,0 +1,96 @@
# 프론트엔드 디버깅 환경 구축 및 Windsurf 디버깅 가이드
이 문서는 본 프로젝트(Next.js 기반 프론트엔드)에서 Windsurf 및 브라우저/VSCode를 활용해 효율적으로 디버깅하는 방법을 안내합니다.
---
## 1. 개발 환경 준비
### 1) Node.js 및 패키지 설치
- Node.js 18.x 이상 필요
- 루트 디렉토리에서:
```bash
npm install
# 또는
pnpm install
```
### 2) 환경 변수 파일 준비
- `.env.local` 등 환경 변수 파일을 복사/생성합니다.
- API 엔드포인트 등 민감 정보는 환경 변수로 관리하세요.
### 3) Next.js 개발 서버 실행
- 아래 명령어로 개발 서버 실행:
```bash
npm run dev
# 또는
pnpm dev
```
- 기본 포트: `3000` (필요시 `.env` 또는 `package.json`에서 변경)
---
## 2. Windsurf 디버깅 환경 구축
### 1) 소스맵 활성화
- Next.js는 개발 모드에서 자동으로 소스맵을 제공합니다.
- 별도 설정 없이 브라우저에서 TypeScript/JSX 소스 디버깅 가능.
### 2) 브라우저 디버깅
- Chrome/Edge 등에서 F12 → `Sources` 탭에서 실제 소스 파일에 브레이크포인트 설정.
- step-by-step 실행, 변수 값 확인, 콜스택 추적 가능.
### 3) VSCode 디버깅 (선택)
- `.vscode/launch.json` 파일 생성:
```json
{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Next.js: Chrome 브라우저에서 디버깅",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}"
}
]
}
```
- VSCode에서 디버깅 세션 시작 → 브레이크포인트, 변수 추적 가능.
### 4) Windsurf 연동
- Windsurf에서 “로컬 서버 연결” 기능으로 `http://localhost:3000` 접속.
- 브라우저 프리뷰, 콘솔 로그, 네트워크 요청, 소스맵 기반 디버깅 가능.
- "디버깅 세션 시작" 버튼 클릭 시 브라우저 개발자 도구와 동일하게 동작.
---
## 3. 디버깅 팁
- **핫 리로드**: 코드 수정 시 자동 반영.
- **React DevTools**: 컴포넌트 상태, props, hooks 실시간 확인.
- **네트워크 탭**: API 요청/응답 실시간 확인.
- **에러 오버레이**: Next.js가 런타임/빌드 에러를 화면에 표시.
---
## 4. 문제 해결 체크리스트
- **빌드/런타임 에러**: 터미널과 브라우저 메시지 확인.
- **API 통신 문제**: 네트워크 탭에서 요청/응답 확인, 환경 변수 및 서버 상태 점검.
- **소스맵 미작동**: `next.config.js` 등에서 소스맵 관련 옵션 확인.
---
## 5. Windsurf 디버깅 절차 요약
1. 의존성 설치 및 개발 서버 실행 (`npm run dev`)
2. Windsurf에서 “로컬 서버 연결”로 `http://localhost:3000` 접속
3. 브라우저 프리뷰에서 UI/네트워크/콘솔/소스 디버깅
4. 필요시 VSCode에서 브레이크포인트 디버깅 병행
---
## 참고
- Next.js 공식문서: https://nextjs.org/docs
- Windsurf 공식문서 및 지원 채널 참고
+214
View File
@@ -0,0 +1,214 @@
# Project Improvement Plan
This document outlines a detailed plan for improving the frontend project based on the requirements and goals specified in `requirements.md`.
## Executive Summary
After analyzing the project requirements, we've identified several key areas for improvement to enhance the developer experience, code quality, performance, and user experience across all applications. This plan provides a roadmap for implementing these improvements in a phased approach, considering the constraints and future considerations outlined in the requirements.
## Architecture Improvements
### Component Library Enhancement
**Rationale**: The shared component library is central to achieving unified user experience and code reusability goals.
**Action Items**:
1. Audit existing components in `packages/common/components` to identify gaps and inconsistencies
2. Implement missing core components based on Shadcn UI patterns
3. Enhance component documentation with usage examples and best practices
4. Create a component development checklist to ensure all new components meet accessibility standards
5. Implement a versioning strategy for components to manage breaking changes
### Monorepo Structure Optimization
**Rationale**: Optimizing the monorepo structure will improve build times and developer experience.
**Action Items**:
1. Review and optimize Turborepo cache configuration
2. Implement project references in TypeScript configuration for better type checking
3. Create clear guidelines for code organization within each application
4. Establish conventions for sharing code between applications
5. Implement better dependency management to prevent version conflicts
### State Management Standardization
**Rationale**: Consistent state management patterns will improve maintainability and reduce bugs.
**Action Items**:
1. Create standardized Zustand store patterns for common use cases
2. Implement store persistence where appropriate
3. Develop utilities for store debugging and testing
4. Document best practices for state management
5. Create examples of complex state management scenarios
## Performance Optimization
### Bundle Size Reduction
**Rationale**: Meeting the 500KB compressed bundle size constraint requires careful optimization.
**Action Items**:
1. Implement bundle analysis in the CI pipeline
2. Set up code splitting for all applications
3. Optimize image loading and processing
4. Implement tree-shaking for all dependencies
5. Create guidelines for importing third-party libraries
### Runtime Performance
**Rationale**: Achieving the FCP and TTI targets requires optimizing the critical rendering path.
**Action Items**:
1. Implement performance monitoring in production
2. Optimize component rendering with React.memo and useMemo
3. Implement lazy loading for non-critical components
4. Optimize CSS delivery and rendering
5. Create performance testing scenarios for CI
## Developer Experience
### Documentation Improvements
**Rationale**: Better documentation will help teams with varying levels of expertise work effectively.
**Action Items**:
1. Create a comprehensive developer onboarding guide
2. Document common patterns and solutions
3. Implement automated documentation generation for components
4. Create interactive examples for complex components
5. Establish a process for keeping documentation up-to-date
### Tooling Enhancements
**Rationale**: Better tooling will improve developer productivity and code quality.
**Action Items**:
1. Enhance ESLint and Prettier configurations for stricter code quality checks
2. Implement pre-commit hooks for code formatting and linting
3. Create custom VS Code extensions for project-specific tasks
4. Implement automated dependency updates with Renovate
5. Create custom CLI tools for common development tasks
## Testing Strategy
### Component Testing
**Rationale**: Comprehensive testing is essential for maintaining quality as the codebase grows.
**Action Items**:
1. Establish minimum test coverage requirements for components
2. Implement visual regression testing with Storybook
3. Create testing utilities for common testing scenarios
4. Implement accessibility testing in the CI pipeline
5. Document testing best practices and patterns
### Integration Testing
**Rationale**: Integration testing ensures that components work together as expected.
**Action Items**:
1. Implement end-to-end testing with Cypress
2. Create testing environments for each application
3. Implement API mocking for testing
4. Create test data generators for common scenarios
5. Implement performance testing in the CI pipeline
## Accessibility Improvements
### Accessibility Standards
**Rationale**: Meeting accessibility requirements is a core goal of the project.
**Action Items**:
1. Conduct an accessibility audit of existing components
2. Implement ARIA attributes and keyboard navigation for all components
3. Create accessibility testing guidelines
4. Implement automated accessibility testing in the CI pipeline
5. Provide training on accessibility best practices
### Responsive Design
**Rationale**: Ensuring responsive design is essential for a good user experience on all devices.
**Action Items**:
1. Establish responsive design guidelines
2. Create responsive design testing scenarios
3. Implement responsive design utilities
4. Optimize for mobile devices
5. Create responsive design documentation
## Technical Debt Reduction
### Legacy Code Migration
**Rationale**: Gradually migrating legacy code will improve maintainability and reduce technical debt.
**Action Items**:
1. Identify and prioritize legacy code for migration
2. Create a migration plan for each legacy component
3. Implement automated tests for legacy code
4. Create migration guidelines and best practices
5. Establish metrics for tracking migration progress
### Code Quality Improvements
**Rationale**: Improving code quality will reduce bugs and improve maintainability.
**Action Items**:
1. Implement stricter TypeScript configurations
2. Create code quality metrics and dashboards
3. Implement code reviews for all pull requests
4. Create code quality guidelines
5. Implement automated code quality checks in the CI pipeline
## Future-Proofing
### Internationalization Preparation
**Rationale**: Preparing for internationalization now will make it easier to implement in the future.
**Action Items**:
1. Research and select an internationalization library
2. Create guidelines for internationalization-friendly component design
3. Implement a proof-of-concept for internationalization
4. Document internationalization best practices
5. Create a plan for implementing internationalization across all applications
### Theming Support
**Rationale**: Implementing theming support will enable white-labeling in the future.
**Action Items**:
1. Research and select a theming approach
2. Implement a proof-of-concept for theming
3. Create theming guidelines for component development
4. Document theming best practices
5. Create a plan for implementing theming across all applications
## Implementation Timeline
### Phase 1: Foundation (Months 1-2)
Focus on architecture improvements, developer experience, and establishing standards.
### Phase 2: Quality and Performance (Months 3-4)
Focus on testing, performance optimization, and accessibility improvements.
### Phase 3: Technical Debt and Future-Proofing (Months 5-6)
Focus on technical debt reduction and preparing for future requirements.
## Success Metrics
To measure the success of this improvement plan, we will track the following metrics:
1. **Build Performance**: CI/CD pipeline completion time, local build time
2. **Runtime Performance**: FCP, TTI, bundle size
3. **Code Quality**: Test coverage, TypeScript strict mode compliance
4. **Developer Experience**: Time to onboard new developers, developer satisfaction surveys
5. **User Experience**: Accessibility compliance, user satisfaction surveys
## Conclusion
This improvement plan provides a comprehensive roadmap for enhancing the frontend project in line with the requirements and goals specified in `requirements.md`. By following this plan, we can create a more maintainable, performant, and user-friendly frontend while enabling team autonomy and code reusability.
+78
View File
@@ -0,0 +1,78 @@
# Project Requirements
This document outlines the key requirements, goals, and constraints for the frontend project.
## Core Goals
1. **Unified User Experience**: Create a consistent user experience across all applications within the monorepo.
2. **Code Reusability**: Maximize code reuse through shared components and utilities.
3. **Team Autonomy**: Allow different teams to work independently on their applications while maintaining consistency.
4. **Performance Optimization**: Ensure fast loading times and smooth user interactions across all applications.
5. **Accessibility**: Make all applications accessible to users with disabilities.
6. **Maintainability**: Ensure the codebase is easy to maintain and extend over time.
## Technical Requirements
### Architecture
1. Maintain the monorepo structure using pnpm workspaces and Turborepo.
2. Follow the established project structure:
- `apps/`: Contains the applications
- `common/`: Common features provided by the main company
- `cs/`, `wr/`, `as/`: Applications developed by different teams
- `packages/`: Contains shared packages
- `common/`: Common packages provided by the main company
- `assets/`: Shared assets (styles, images, etc.)
- `components/`: Shared UI components
- `business_components/`: Business-specific components
- `config/`: Shared configuration
- `network/`: Network-related utilities
### Development Standards
1. Use TypeScript for all code to ensure type safety.
2. Follow ESLint and Prettier configurations for consistent code style.
3. Implement comprehensive testing using Storybook for component development and testing.
4. Use Zustand for state management across applications.
5. Implement TanStack Query (React Query) for data fetching and caching.
6. Use Axios for HTTP requests.
### UI/UX Requirements
1. Use Tailwind CSS for styling with consistent design patterns.
2. Implement Radix UI for accessible UI primitives.
3. Follow Shadcn UI patterns for component design.
4. Use TanStack Table for data tables and Chart.js for charts.
5. Ensure responsive design for all screen sizes.
6. Maintain a consistent color scheme and typography across all applications.
## Constraints
1. **Performance Budgets**:
- First Contentful Paint (FCP): < 1.8s
- Time to Interactive (TTI): < 3.5s
- Total Bundle Size: < 500KB (compressed)
2. **Browser Support**:
- Support the last two versions of major browsers (Chrome, Firefox, Safari, Edge)
- No support required for Internet Explorer
3. **Deployment Constraints**:
- CI/CD pipeline must complete within 10 minutes
- Zero-downtime deployments required
4. **Team Constraints**:
- Multiple teams working on different applications
- Need for clear boundaries and interfaces between teams
- Varying levels of expertise with the technology stack
5. **Technical Debt**:
- Some legacy code exists in certain applications
- Need to gradually migrate to the new architecture
## Future Considerations
1. **Internationalization**: Prepare for multi-language support in the future.
2. **Theming**: Consider implementing a theming system for white-labeling.
3. **Mobile Applications**: Prepare for potential React Native integration.
4. **Micro-frontends**: Evaluate the possibility of moving to a micro-frontend architecture.