본문 바로가기
TIL

12. 23. 39일차 TIL 효율적인 파일 구조와 렌더링 방식

by 눈 새 2024. 12. 24.

Next 팀 프로젝트가 시작되었다. 주제를 선정한 뒤 와이어프레임을 작성하고, MVP를 설정했다. 이어서 각 페이지의 렌더링 방식을 논의해 결정하고, 프로젝트 셋업까지 마무리했다. 그러므로 오늘은 효율적인 파일 구조와 렌더링 방식을 정리해보려고 한다.


1. Next.js의 기본 규칙과 폴더

Next.js는 파일 기반 라우팅 시스템을 제공하므로 파일 구조를 어떻게 설계하느냐에 따라 프로젝트의 유지보수성과 가독성이 크게 달라진다.

  • 유지보수성 : 프로젝트가 커질수록 코드가 복잡해지는데, 폴더 구조가 페계적이면 유지 보수가 훨씬 용이해진다.
  • 팀 협업 : 여러 명이 작업할 때 공통된 구조가 있으면 코드 탐색과 작업 분배가 쉬워진다.
  • 재사용성과 확장성 : 재사용할 컴포넌트를 모으고, 특정 기능별 코드를 분리해두면 확장 시 충돌을 줄일 수 있다.

 

1) App Router와 라우트 구조

  • Next.js 13+ 버전에서는 App Router를 통해 폴더와 파일 이름만으로 라우팅을 설정한다.
  • page.jsx (page.tsx) 파일이 들어 있는 폴더 자체가 라우트(경로)가 된다.
  • route.js (page.ts) 파일로 API 라우트를 작성할 수도 있다.

 

2) 비공개 폴더 (Private Folders)

  • 폴더 이름 앞에 언더스코어(_)를 붙이면 해당  폴더는 라우팅 시스템에서 제외된다.

 

3) 라우터 그룹 (Router Groups)

  • 폴더 이름을 괄호()로 감싸면, URL 경로에는 포함되지 않지만 파일 구조를 조직화하는 데 사용할 수 있다.

 

4) Safe Colocation by Default

  • page.tsx나 route.ts 파일이 없으면 폴더 구조 안에 다른 파일을 안전하게 배치할 수 있다.

 

5) src 디렉터리와 모듈 경로 별칭

  • 프로젝트 루트가 복잡해지는 것을 막기 위해 src 폴더를 사용하여 코드를 분리할 수 있다.
  • import 경로를 짧게 만들기 위해 tsconfig.json 이나 jsconfig.json 에서 경로 별칭(@/)을 설정할 수 있다.

2. 효율적인 파일 구조 설계 전략

 

1) 단순하고 평평한 구조

// 단순하고 평평한 구조 예시

my-app/
├─ app/
│  ├─ home/
│  │  ├─ page.tsx
│  ├─ about/
│  │  ├─ page.tsx
├─ components/
│  ├─ Button.tsx
│  ├─ Header.tsx
│  ├─ Footer.tsx
├─ package.json
└─ README.md
  • 특징
    • Page.tsx 를 제외한 모든 컴포넌트가 components/ 폴더에 모여 있음.
    • 소규모 프로젝트, 개인 학습용 프로젝트에 적합
  • 장점
    • 구조가 단순해 빠르게 구성 가능
    • 컴포넌트가 적으면 유지보수가 쉽다
  • 단점
    • 컴포넌트가 많아지면 폴더 내에서 파일이 뒤엉켜 관리가 어려워질 수 있다.

 

2) 목적 기반 구조

// 목적 기반 구조 예시

my-app/
├─ app/
│  ├─ home/
│  │  ├─ page.tsx
│  ├─ about/
│  │  ├─ page.tsx
├─ components/
│  ├─ common/
│  │  ├─ Button.tsx
│  │  ├─ Icon.tsx
│  ├─ layout/
│  │  ├─ Header.tsx
│  │  ├─ Footer.tsx
│  ├─ forms/
│  │  ├─ LoginForm.tsx
│  │  ├─ ContactForm.tsx
│  ├─ ui/
│  │  ├─ Modal.tsx
│  │  ├─ Tabs.tsx
├─ package.json
└─ README.md
  • 특징
    • 컴포넌트를 목적(기능적 유사성)에 따라 분류
    •  common, layout, forms, ui, 등으로 나누어 관리
  • 장점
    • 파일 검색이 수월해지고, 코드 위치가 명확함
    • 협업 시에도 폴더 명칭만으로 해당 컴포넌트 목적을 빠르게 파악 가능
  • 단점
    • 명확히 구분하기 애매한 컴포넌트가 발생할 수 있음
    • 시간이 지날수록 목적 범위가 애매해질 때 폴더 재구조화가 필요할 수 있음.

 

3) 기능 / 도메인 기반 구조

// 기능 / 도메인 기반 구조 예시

my-app/
├─ app/
│  ├─ user/
│  │  ├─ page.tsx
│  ├─ product/
│  │  ├─ page.tsx
├─ components/
│  ├─ user/
│  │  ├─ UserProfile.tsx
│  │  ├─ UserList.tsx
│  ├─ product/
│  │  ├─ ProductDetail.tsx
│  │  ├─ ProductList.tsx
│  ├─ blog/
│  │  ├─ BlogPost.tsx
│  │  ├─ BlogList.tsx
  • 특징
    • 특정 기능 (도메인) 별로 코드를 모음 : user, product, blog, 등
    • 대규모 프로젝트에서 많이 채택되는 방식
  • 장점
    • 한 폴더 안에 그 기능과 관련된 모든 컴포넌트가 모여 관리가 용이
    • 팀별 / 기능별로 개발 영역을 분리하기 좋음.
  • 단점
    • 기능 간 공통 로직이나 컴포넌트가 중복될 수 있음.

 

4) Atomic Design 기반 구조

// Atomic Design 기반 구조 예시

my-app/
├─ app/
│  ├─ home/
│  │  ├─ page.tsx
├─ components/
│  ├─ atoms/
│  │  ├─ Button.tsx
│  │  ├─ Input.tsx
│  ├─ molecules/
│  │  ├─ SearchBar.tsx
│  │  ├─ UserCard.tsx
│  ├─ organisms/
│  │  ├─ Header.tsx
│  │  ├─ Footer.tsx
├─ package.json
└─ README.md
  •  특징
    • UI를 작은 원자(Atoms)부터 더 큰 생물체(Organisms)까지 단계적으로 조합하는 구조
    • 디자인 시스템과 컴포넌트 라이브러리를 강력하게 설계할 때 유용
  • 장점
    • 컴포넌트를 체계적으로 관리하여 재사용성과 일관성을 극대화
    • 디자인적 측면에서 탄탄한 시스템 확장 가능
  • 단점
    • Atomic Design 개념 이해가 필요해, 초보자에게 난이도가 높음
    • 모든 컴포넌트를 단계별로 분류하기가 까다로울 수 있음
    • 대게는 디자인 시스템이 먼저 갖춰져야함

3. _component 폴더 패턴 : 페이지별 컴포넌트 관리

"해당 페이지에서만 쓰이는 컴포넌트" 를 페이지 폴더 내부에 _component폴더로 분리하는 패턴이 있다.

 

1) 구조 예시

// _component 폴더 구조 예시

app/
├─ home/
│  ├─ page.tsx
│  ├─ _component/
│  │  ├─ HomeBanner.tsx
│  │  ├─ HomeCTA.tsx
├─ about/
│  ├─ page.tsx
│  ├─ _component/
│  │  ├─ TeamSection.tsx
│  │  ├─ AboutHero.tsx
  • 사용목적
    • 페이지별로만 쓰이는 컴포넌트를 따로 두어 전역 components/ 폴더와 구분하고자 할 때,
    • 재사용성이 거의 없거나, 해당 페이지에서만 필요하다고 확실히 판단되는 경우
  • 장점
    • 명확한 코드 경계 : 페이지별로 관련된 컴포넌트를 한눈에 파악 가능
    • 전역 컴포넌트와의 충돌 방지 : 재사용 의도가 없는 컴포넌트를 별도로 관리
  • 단점 
    • 중복 가능성 : 비슷한 컴포넌트가 다른 페이지에서도 필요하면 중복 발생
    • 폴더 구조 복잡화 : 페이지마다 _component가 늘어날 수 있음.

4. Index 파일 활용

index 파일을 활용하면 모듈 및 컴포넌트의 재사용성과 가독성을 높이고, 코드 작성 및 유지보수를 간소화할 수 있다. Next.js와 같은 프로젝트에서 index 파일은 폴더 내에서 파일을 단일 진입점으로 만들어 효율적인 모듈 관리와 구조화를 가능하게 한다.

 

1) Index 파일

index.js , index.ts, 또는 index.tsx 파일은 디렉토리의 기본 진입점 역할을 한다. 디렉토리에서 index 파일을 생성하면 해당 폴더를 경로로 지정할 때 자동으로  index 파일을 불러올 수 있다.

 

예를 들어 :

components/
├── Button.tsx
├── Header.tsx
└── index.ts

 

index.ts를 통해 내보내기를 설정했다면, 다음과 같이 컴포넌트를 가져올 수 있다.

import { Button, Header } from './components';

 

2) Index 파일 활용의 장점

  • 간결한 Import 경로
  • 재사용성 향상
  • 구조적 일관성 유지
  • 코드 정리 및 관리 용이

 

3) Index 파일 관리 시 주의점

  • 불필요한 내보내기 방지 : 필요한 모듈까지 모두 내보내면, 사용되지 않는 코드가 많아져 가독성이 떨어질 수 있다.
  • 파일 충돌 방지 : 동일한 이름의 모듈을 내보낼 때 이름 충돌이 발생할 수 있으므로 적절한 네이밍 규칙을 설정해야한다.
  • Tree Shaking 고려 : index 파일을 사용할 경우, 사용하지 않는 모듈까지 번들링되는 문제가 발생할 수 있다.

5. Next의 렌더링

Next.js 는 다양한 렌더링 방식을 제공하여 애플리케이션의 성능과 UX를 최적화할 수 있다.주요 렌더링 방식은 다음 4가지이다.

 

1) Static Site Generation (SSG)

  • 개념 : 빌드 시점에 HTML을 생성하여 모든 사용자에게 동일한 정적 HTML 파일을 제공하는 방식
  • 특징
    • 빠른 초기 로딩 속도 : HTML 파일이 정적으로 준비되어 있어 요청 시 서버 작업 없이 즉시 제공
    • SEO 친화적 : 검색 엔진 크롤러가 오나전히 렌더링된 HTML을 가져갈 수 있음.
    • 주로 변경이 적고 고정된 콘텐츠를 가진 페이지에 적합
  • 사용법 : getStaticProps 함수를 사용하여 데이터를 가져오고 정적 HTML을 생성한다.
export async function getServerSideProps(context) {
  const data = await fetchData();
  return {
    props: { data },
  };
}
  • 사용 예시
    • 블로그 게시글
    • 마케팅 웹사이트
    • 제품 소개 페이지

 

2) Server Side Rendering (SSR)

  • 개념 : 각 요청마다 서버에서 HTML을 생성하여 사용자에게 제공하는 방식
  • 특징
    • 동적인 데이터 : 요청마다 초신 데이터를 기반으로 페이지가 생성됨
    • SEO 친화적 : 검색 엔진 크롤러가 서버에서 렌더링된 HTML을 가져갈 수 있음.
    • 주로 실시간 데이터가 필요한 페이지에 적합
  • 사용법 : getServerSideProps 함수를 사용하여 요청 시마다 데이터를 가져옴
export async function getServerSideProps(context) {
  const data = await fetchData();
  return {
    props: { data },
  };
}
  • 사용 예시
    • 사용자별 대시보드
    • 실시간 업데이트가 필요한 페이지 (예 : 뉴스, 주식 차트)

 

3) Client Side Rendering (CSR)

  • 개념 : HTML이 클라이언트에 전달된 후, 브라우저에서 JS로 데이터를 가져와 렌더링하는 방식
  • 특징
    • 초기 로딩 시 서버에서 HTML을 최소화한 상태로 전달하고, 이후 데이터를 클라이언트에서 가져옴
    • 주로 SEO가 중요하지 않고, 사용자 상호작용이 많은 페이지에 적합
    • 빠른 초기 로딩이 가능하지만 S가 비활성화된 환경에서는 콘텐츠가 표시되지 않음.
  • 사용법 : REact의 useEffect 또는 클라이언트 라이브러리를 사용하여 데이터 패칭
import { useState, useEffect } from 'react';

function Page() {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch('/api/data')
      .then((res) => res.json())
      .then((data) => setData(data));
  }, []);

  return <div>{data ? data.content : 'Loading...'}</div>;
}
  • 사용 예시
    • 대화형 웹 애플리케이션
    • SPA (Single Page Applicatin) 스타일 페이지

 

4) Incremental Static Regeneration (ISR)

  • 개념 : SSG와 비슷하지만, 정적 페이지를 주기적으로 재생성하여 새로운 콘텐츠를 제공하는 방식
  • 특징
    • 최신 데이터 제공 : 지정된 재생성 간격 (revalidate)마다 페이지가 다시 생성됨
    • 성능과 실시간 데이터 업데이트의 균형을 제공
    • 주로 데이터가 자주 변겨오디지만, 실시간일 필요는 없는 페이지에 적합
  • 사용법
export async function getStaticProps() {
  const data = await fetchData();
  return {
    props: { data },
    revalidate: 10, // 10초마다 페이지 갱신
  };
}
  • 사용 예시
    • 제품 목록 페이지
    • 블로그 게시글 목록
    • 이벤트 페이지

6. 렌더링 방식 요약 및 비교

 

1) HTML 생성 시점

  • Static Site Generation (SSG) : 빌드 시점
  • Server Side Rendering (SSR) : 요청 시점
  • Client Side Rendering (CSR) : 클라이언트에서 렌더링
  • Incremental Static Regeneration (ISR) : 빌드 시점 + 갱신 주기 설정

 

2) 사용 사례

  • Static Site Generation (SSG) : 고정된 콘텐츠
  • Server Side Rendering (SSR) : 실시간 데이터
  • Client Side Rendering (CSR) : 사용자 상호작용 중심
  • Incremental Static Regeneration (ISR) : 주기적으로 변경되는 콘텐츠

 

3) SEO

  • Static Site Generation (SSG) : ✔
  • Server Side Rendering (SSR) : ✔
  • Client Side Rendering (CSR) : X
  • Incremental Static Regeneration (ISR) : ✔

 

4) 성능

  • Static Site Generation (SSG) : 🚀 빠름
  • Server Side Rendering (SSR) : ⏳ 느림
  • Client Side Rendering (CSR) : 🚀 빠름
  • Incremental Static Regeneration (ISR) : 🚀 빠름

 

7. 진행되고 있는 프로젝트 와이어 프레임

 

하지만 디자인 시안은 아직이다 .. 내일은 디자인 마무리하고 ui까지는 그릴 수 있도록 열심히 해봐야겠다..