본문 바로가기

sparta/수료이후 공부

day1 - Next.js 프로젝트 폴더 구조 & 역할 정리

학습목표
1. 폴더별 역할 및 주요 개념을 간단하고 명확하게 정리할 수 있다.
2. 페이지 라우팅과 API  Routes를 포함한 전체구조를 이해할 수 있다.
3. 파일별 기능을 빠르게 파악하고 유지보수 시 어디를 수정해야 하는지 쉽게 찾을 수 있다.

 

 

프로젝트 폴더구조 요약

src/
├── app/                 → 페이지 라우팅 (Next.js App Router)
│   ├── layout.tsx       → 공통 레이아웃
│   ├── page.tsx         → 메인 페이지 ("/")
│   ├── champions/
│   │   ├── page.tsx     → 챔피언 목록 페이지 ("/champions")
│   │   ├── [id]/page.tsx → 개별 챔피언 상세 페이지 ("/champions/:id")
│   ├── api/
│   │   ├── rotation/route.ts → API Routes ("/api/rotation")
│  
├── components/          → 재사용 가능한 UI 컴포넌트
│   ├── ui/              → 버튼, 모달 같은 공통 UI 컴포넌트
│   ├── layout/          → Header, Footer, Sidebar 등
│   ├── features/        → 특정 기능 컴포넌트 (검색창, 페이지네이션)
│
├── api/                 → API 요청 함수 (Supabase, Riot API, fetch, axios)
│   ├── supabaseClient.ts → Supabase 설정
│   ├── riotApi.ts       → Riot API 연동
│   ├── queries/         → 챔피언, 아이템, 로테이션 API 요청 함수 정리
│
├── types/               → TypeScript 타입 정의 (데이터 구조)
│   ├── Champion.ts      → 챔피언 타입
│   ├── User.ts          → 유저 타입
│  
├── utils/               → 공통적으로 사용하는 함수 (포맷팅, 날짜 변환 등)
│   ├── formatDate.ts    → 날짜 변환 함수
│   ├── validation.ts    → 입력값 검증 함수
│  
├── styles/              → 전역 스타일 (Tailwind CSS 설정)
│  
└── public/              → 정적 파일 (이미지, 아이콘 등)

 


프로젝트 폴더 & 파일 구조 확인

 

src/폴더 내 주요 파일들을 하나씩 열어보고 역할 정리하기

1. 페이지 라우팅 (Next.js 14 - App Router)

(1) 기본 페이지 라우팅

앱라우터(src/app/)를 기반으로 페이지 라우팅이 자동으로 설정된다.

파일과 폴더 구조만으로 페이지가 생성되고 별도의 react-router-dom설정이 필요없다.

src/app/
├── page.tsx              → 루트 페이지 ("/")
├── champions/
│   ├── page.tsx          → 챔피언 리스트 페이지 ("/champions")
│   ├── [id]/
│   │   ├── page.tsx      → 개별 챔피언 상세 페이지 ("/champions/:id")
├── items/
│   ├── page.tsx          → 아이템 리스트 페이지 ("/items")
├── rotation/
│   ├── page.tsx          → 로테이션 페이지 ("/rotation")

즉, 폴더가 url경로가 된다.

 

(2) 동적 라우팅 (Dynamic Routes/동적세그먼트)

[id] 같은 대괄호를 사용하면 url이 동적으로 변경가능하다.

// src/app/champions/[id]/page.tsx
export default function ChampionDetail({ params }: { params: { id: string } }) {
  return <div>챔피언 상세 페이지 - ID: {params.id}</div>;
}
// /champions/1 → { params: { id: "1" } }
// /champions/ahri → { params: { id: "ahri" } }
// /champions/zed → { params: { id: "zed" } }

즉, [id]를 사용하면 url에 따라 다르게 동작하는 페이지를 만들 수 있다.

기존 react-router-dom의 useParams() 대신 params객체를 사용한다.

 

(3) 레이아웃 (공통 UI 설정)

layout.tsx파일을 사용하면 모든 하위 페이지에 적용되는 공통 레이아웃을 만들 수 있다.

src/app/
├── layout.tsx           → 전체 페이지에 적용되는 공통 레이아웃
├── page.tsx             → 메인 페이지
├── champions/
│   ├── layout.tsx       → 챔피언 관련 페이지 공통 레이아웃
│   ├── page.tsx         → 챔피언 리스트
│   ├── [id]/
│   │   ├── page.tsx     → 챔피언 상세 페이지
// src/app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko">
      <body>
        <nav>네비게이션 바</nav>
        {children}
      </body>
    </html>
  );
}

모든 페이지에 자동으로 <nav>네비게이션 바</nav>가 추가된다.

하위 폴더에 layout.tsx가 있으면 해당 폴더의 페이지들에만 적용된다.

 

2. API Routes & API 호출 흐름 정리

서버없이도 백엔드 API를 만들 수 있다. src/app/api 내부에서 설정해야 하며 src/api 폴더는 외부 API와 데이터를 주고받는 로직을 모아두는 곳이다. app/api폴더를 사용하여 API엔드포인트를 직접 만들 수 있다.

fetch(), axios, useQuery등 사용하여 API 요청을 처리한다. API관련 로직을 따로 분리해서 유지보수가 쉬워지고 클라이언트 컴포넌트에서 직접 API 호출을 하지 않고 api/폴더에서 처리 후 가져다 쓰면 된다.

 

(1) app/api/ 폴더 내부의 route.ts에서 API 엔드포인트 생성 가능.

// app/api/rotation/route.ts
export async function GET() {
  const data = await fetch("https://riot-api.com/rotation").then((res) => res.json());
  return Response.json(data);
}

(2) API 요청 관리 (src/api/ 폴더)

// src/api/queries/champions.ts
import { supabase } from "../supabaseClient";

export async function getChampions() {
  const { data, error } = await supabase.from("champions").select("*");
  if (error) throw new Error(error.message);
  return data;
}

(3) useQuery를 사용한 API 요청

import { useQuery } from "@tanstack/react-query";
import { getChampions } from "@/api/queries/champions";

export default function ChampionList() {
  const { data: champions, error, isPending } = useQuery({
    queryKey: ["champions"],
    queryFn: getChampions,
  });

  if (isPending) return <div>로딩 중...</div>;
  if (error) return <div>에러 발생: {error.message}</div>;

  return (
    <ul>
      {champions.map((champion) => (
        <li key={champion.id}>{champion.name}</li>
      ))}
    </ul>
  );
}
API를 직접 만들 땐 src/app/api/ (Next.js API Routes 사용)
외부 API와 연결할 땐 src/api/ (fetch, axios, supabase 사용

3. 재사용 가능한 UI 컴포넌트

넥스트에서는 src/components/에 여러 페이지에서 공통으로 쓰이는 UI 요소들을 모아두는 곳이다.

페이지마다 반복되는 UI를 따로 컴포넌트로 만들어서 관리하고 재사용이 가능한 컴포넌트를 따로 두면 유지보수와 코드관리가 쉬워진다. (예를들면, Button, Modal, Header, Footer, Search, Pagenaion 등)

src/components/
├── ui/                  → 버튼, 모달, 카드 같은 기본 UI 컴포넌트
│   ├── Button.tsx       → 공통 버튼 컴포넌트
│   ├── Modal.tsx        → 공통 모달 컴포넌트
│   ├── Card.tsx         → 공통 카드 컴포넌트
│   ├── Loading.tsx      → 로딩 화면 컴포넌트
│   ├── Error.tsx        → 에러 메시지 컴포넌트
│
├── layout/              → 페이지 공통 레이아웃 컴포넌트
│   ├── Header.tsx       → 상단 네비게이션 바
│   ├── Footer.tsx       → 하단 푸터
│   ├── Sidebar.tsx      → 사이드바 메뉴
│
├── features/            → 특정 기능을 담당하는 컴포넌트
│   ├── SearchBar.tsx    → 검색창 UI
│   ├── Pagination.tsx   → 페이지네이션 UI
│
├── champions/           → 챔피언 관련 UI 컴포넌트
│   ├── ChampionCard.tsx → 챔피언 카드 (이름, 이미지 표시)
│   ├── ChampionList.tsx → 챔피언 목록 UI
│
└── items/               → 아이템 관련 UI 컴포넌트
    ├── ItemCard.tsx     → 아이템 카드 UI
    ├── ItemList.tsx     → 아이템 목록 UI

 

 

4. TypeScript 타입 정의 (인터페이스, 타입 선언)

데이터 구조(객체, API응답 값 등)의 타입을 미리 정의해서 코드 안정성을 높이기 위한 폴더다.

  • type vs interface 차이점

interface는 확장이 가능하며 type은 불가능하다.

interface는 주로 객체구조 정의이고 type은 객체, 유니언 타입, 튜플 등 다양하게 사용된다.

interface는 유니언타입이 불가능하다.

  • interface 
interface User {
  id: string;
  username: string;
  email: string;
}
const user: User = {
  id: "123",
  username: "john_doe",
  email: "john@example.com",
};
  • type 
type User = {
  id: string;
  username: string;
  email: string;
};
const user: User = {
  id: "123",
  username: "john_doe",
  email: "john@example.com",
};

 

(1) type은 유니언 타입(|)을 사용할 수 있음.

type UserRole = "user" | "admin" | "moderator";
const role: UserRole = "admin"; // ✅ 정상
const invalidRole: UserRole = "superuser"; // ❌ 오류 발생 (정의되지 않은 값)

 

(2) type은 튜플(배열 타입)도 정의 가능.

type ChampionStats = [number, number, number]; // [공격력, 방어력, 마나]

type Champion = {
  id: string;
  name: string;
  stats: ChampionStats;
};
const ahri: Champion = {
  id: "ahri",
  name: "아리",
  stats: [50, 20, 300], // ✅ 정상
};

const invalidChampion: Champion = {
  id: "zed",
  name: "제드",
  stats: [300, "강함", 50], // ❌ 오류 발생 (stats의 순서가 맞지 않음)
};

 

 

  • type interface vs type 확장(상속)

(1) extends

interface User {
  id: string;
  username: string;
}

interface Admin extends User {
  role: "admin";
  permissions: string[];
}
const admin: Admin = {
  id: "123",
  username: "admin_user",
  role: "admin",
  permissions: ["edit", "delete"],
};

 

(2) &

type User = {
  id: string;
  username: string;
};

type Admin = User & {
  role: "admin";
  permissions: string[];
};
const admin: Admin = {
  id: "123",
  username: "admin_user",
  role: "admin",
  permissions: ["edit", "delete"],
};

 

5. Utils, 공통적으로 사용하는 함수 (포맷팅, 날짜 변환 등) <= UI랑 연관 X

src/utils/는 여러곳에서 공통적으로 사용되는 유틸리티 함수를 모아두는 곳이다.

컴포넌트와 데이터 처리 로직(비즈니스 로직)을 분리해야 할 때 utils/ 폴더가 필요하다.

src/utils/
├── formatDate.ts       → 날짜 형식 변환 함수
├── numberUtils.ts      → 숫자 관련 유틸 함수 (포맷팅, 변환 등)
├── stringUtils.ts      → 문자열 관련 유틸 함수 (대소문자 변환, 공백 제거 등)
├── fetchUtils.ts       → API 요청 관련 유틸 함수 (GET, POST 요청)
├── validation.ts       → 입력값 검증 함수 (이메일, 비밀번호 유효성 검사 등)

 

app/ 폴더 기준으로 어떤 페이지들이 있는지 정리하기


1. app/ 폴더의 역할
app/ 폴더는 페이지라우팅을 담당하는 곳이다. 폴더와 파일구조가 곧 URL구조 결정된다.

page.tsx 파일이 있는 폴더가 페이지 역할을 하며 [id]동적라우팅을 사용하면 특정 값에 따라 페이지가 다르게 렌더링된다.

 api/ 폴더를 사용하면 서버없이 API 엔드포인트를 생성할 수 있다. 

 

2. app/ 폴더의 기본적인 구조 예제 (롤 정보 앱 기준)

app/
├── 📂 page.tsx                 → 루트 페이지 ("/")
├── 📂 champions/page.tsx       → 챔피언 리스트 페이지 ("/champions")
├── 📂 champions/[id]/page.tsx  → 개별 챔피언 상세 페이지 ("/champions/:id")
├── 📂 items/page.tsx           → 아이템 리스트 페이지 ("/items")
├── 📂 rotation/page.tsx        → 챔피언 로테이션 페이지 ("/rotation")
├── 📂 api/rotation/route.ts    → 챔피언 로테이션 API ("/api/rotation")


3. app/ 내부의 page.tsx 파일들이 의미하는 것

(1) app/page.tsx → 루트 페이지 ("/")

// app/page.tsx
export default function Home() {
  return <h1>메인 페이지</h1>;
}

(2) app/champions/page.tsx → 챔피언 리스트 페이지 ("/champions")

// app/champions/page.tsx
export default function ChampionList() {
  return <h1>챔피언 목록</h1>;
}

(3) app/champions/[id]/page.tsx → 개별 챔피언 상세 페이지

// app/champions/[id]/page.tsx
export default function ChampionDetail({ params }: { params: { id: string } }) {
  return <h1>챔피언 상세 페이지 - ID: {params.id}</h1>;
}

(4) app/items/page.tsx → 아이템 리스트 페이지 ("/items")

// app/items/page.tsx
export default function Items() {
  return <h1>아이템 목록</h1>;
}

(5) app/rotation/page.tsx → 챔피언 로테이션 페이지 ("/rotation")

// app/rotation/page.tsx
export default function Rotation() {
  return <h1>로테이션 챔피언</h1>;
}


4. api/ 폴더가 있다면 API 엔드포인트가 어디서 처리되는지 확인
api/ 폴더 내부에 route.ts를 추가하면 해당 경로에서 API엔드포인트가 자동으로 생성된다.

(1) app/api/rotation/route.ts → 챔피언 로테이션 API ("/api/rotation")

// app/api/rotation/route.ts
export async function GET() {
  const data = await fetch("https://riot-api.com/rotation").then((res) => res.json());
  return Response.json(data);
}
// Roit API에서 챔피언 로테이션 데이터를 받아와 클라이언트에게 반환한다.
// API Route 기능을 이용해서 백엔드 없이도 API 엔드포인트를 만들 수 있다.
// GET()함수가 실행되면 외부 API에서 데이터를 가져와서 JSON으로 반환한다.

 

 

 

 

 

 

 

'sparta > 수료이후 공부' 카테고리의 다른 글

day2 - Next.js 렌더링방식 및 JS 문법  (0) 2025.02.09