(TIL 리팩토링 필요..)
🚀 NextJS 프로젝트 구조를 효율적으로 조직하는 방법
1. Next.js 폴더 구조의 중요성
• 유지보수성: 코드가 복잡해질수록 체계적인 구조는 유지보수를 쉽게 만듭니다.
• 협업 효율성: 팀원 간 작업 분배가 명확해지고 코드 탐색이 빨라집니다.
• 재사용성/확장성: 공통 컴포넌트와 기능별 파일을 분리하여 프로젝트 확장 시 충돌을 방지합니다.
2. Next.js 폴더 구조 및 라우팅 규칙
app/
├─ home/
│ ├─ page.tsx # /home
│ ├─ HomeBanner.tsx # 라우트 안됨 (UI 컴포넌트)
├─ about/
│ ├─ page.tsx # /about
├─ api/
│ ├─ hello/
│ │ └─ route.ts # /api/hello (API 라우트)
├─ (admin)/
│ ├─ dashboard/
│ │ └─ page.tsx # /dashboard (라우트 그룹)
│
├─ _components/ # 전역 컴포넌트 (라우트 안됨)
│ ├─ Button.tsx
│ ├─ Header.tsx
│ ├─ Footer.tsx
• page.tsx: 폴더가 경로가 되며 page.tsx는 해당 라우트로 매핑됩니다.
• API 라우트: route.ts를 사용하여 API 엔드포인트 생성 가능.
• 라우터 그룹: (폴더명)을 사용해 URL에 포함되지 않으면서도 폴더 정리를 할 수 있습니다.
• 비공개 폴더: _components처럼 _로 시작하면 라우팅에서 제외됩니다.
3. 폴더 구조 전략
3-1. 단순 구조 (소규모 프로젝트)
app/
├─ home/
│ ├─ page.tsx
│ └─ Banner.tsx
├─ components/
│ ├─ Button.tsx
│ ├─ Header.tsx
│ └─ Footer.tsx
• components/ 폴더에 모든 컴포넌트 집중.
• 장점: 빠르게 설정 가능.
• 단점: 프로젝트가 커지면 파일이 뒤엉킬 수 있음.
3-2. 기능별 구조 (대규모 프로젝트)
app/
├─ user/
│ ├─ page.tsx
│ ├─ _components/
│ │ ├─ UserProfile.tsx
│ │ ├─ UserCard.tsx
├─ product/
│ ├─ page.tsx
│ ├─ ProductDetail.tsx
│ ├─ ProductList.tsx
• 특정 기능별로 파일을 분리 (user, product).
• 각 폴더 안에 필요한 컴포넌트를 _components에 저장.
• 장점: 기능별로 모든 관련 파일이 한곳에 모임.
• 단점: 공통 컴포넌트가 중복될 가능성.
4. Atomic Design 기반 구조
components/
├─ atoms/
│ ├─ Button.tsx
│ ├─ Input.tsx
├─ molecules/
│ ├─ SearchBar.tsx
│ ├─ UserCard.tsx
├─ organisms/
│ ├─ Header.tsx
│ ├─ Footer.tsx
• Atoms → Molecules → Organisms로 UI를 설계합니다.
• 장점: 재사용성이 높고 디자인 시스템과의 호환성이 뛰어남.
• 단점: 초기 설계 난이도가 높음.
5. Route Handler에서 Supabase 사용 예시
import { createClient } from "@/utils/supabase/server";
export async function POST(request: Request) {
const supabase = await createClient();
const body = await request.json();
const { data, error } = await supabase.from("todos").insert(body);
if (error) {
return new Response(JSON.stringify({ error: error.message }), { status: 400 });
}
return new Response(JSON.stringify({ data }), { status: 200 });
}
• 서버에서 Supabase 클라이언트를 생성해 DB 연산을 수행합니다.
6. 로그인 및 회원가입 예제
"use server";
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { createClient } from '@/utils/supabase/server';
export async function login(formData: FormData) {
const supabase = await createClient();
const data = {
email: formData.get('email') as string,
password: formData.get('password') as string,
};
const { error } = await supabase.auth.signInWithPassword(data);
if (error) redirect('/error');
revalidatePath('/', 'layout');
redirect('/');
}
• 서버 액션으로 로그인 구현.
• 실패 시 에러 페이지로 리다이렉트, 성공 시 메인 페이지로 이동.
7. API 트리거 예시 (회원가입 자동 동기화)
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO public.users (id, email)
VALUES (NEW.id, NEW.email);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW
EXECUTE FUNCTION public.handle_new_user();
• 새 사용자가 auth.users 테이블에 추가되면, users 테이블에 자동 삽입됩니다.
8. 커스텀 훅으로 데이터 로직 분리
기존 (데이터 로직과 UI가 혼재)
export function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/user/${userId}`)
.then(res => res.json())
.then(setUser);
}, [userId]);
return <div>{user?.name}</div>;
}
• 문제점: 데이터 패칭 로직과 UI가 강하게 결합됨.
개선 (커스텀 훅 사용)
// hooks/useUser.ts
import { useEffect, useState } from 'react';
export function useUser(userId: string) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/user/${userId}`)
.then(res => res.json())
.then(setUser);
}, [userId]);
return user;
}
// UserProfile.tsx
import { useUser } from '@/hooks/useUser';
export default function UserProfile({ userId }) {
const user = useUser(userId);
return <div>{user?.name}</div>;
}
• 장점:
• UI와 데이터 로직이 분리되어 가독성이 높아짐.
• 커스텀 훅으로 데이터 로직 재사용 가능.
9. 확장 가능한 API 서비스 (서비스 레이어)
// services/userService.ts
export async function getUser(userId: string) {
const res = await fetch(`/api/user/${userId}`);
if (!res.ok) throw new Error('Failed to fetch user');
return res.json();
}
• API 호출 로직을 서비스 레이어로 분리해 다른 곳에서도 재사용 가능.
10. 서비스 레이어와 커스텀 훅 결합
// UserProfile.tsx
import { useEffect, useState } from 'react';
import { getUser } from '@/services/userService';
export default function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
getUser(userId).then(setUser);
}, [userId]);
return <div>{user?.name}</div>;
}
• 커스텀 훅 없이도 서비스 레이어를 통해 API 호출을 쉽게 확장할 수 있습니다.
'React TIL' 카테고리의 다른 글
[React] Day_75 데일리 정리 (0) | 2025.01.06 |
---|---|
[React] Day_73 심화 프로젝트 후기 (1) | 2025.01.02 |
[React] Day_71 데일리 정리 (0) | 2024.12.30 |
[React] Day_70 오늘의 정리 (0) | 2024.12.27 |
[React] Day_69 팀 프로젝트 작업 관련 트러블슈팅 (1) (0) | 2024.12.26 |