[5주차] Team Hi-Five 권오진 & 박유민 과제 제출합니다.#5
[5주차] Team Hi-Five 권오진 & 박유민 과제 제출합니다.#5waldls wants to merge 104 commits intoCEOS-Developers:masterfrom
Conversation
[DEPLOY] Vercel 프리뷰 및 master 자동 배포 파이프라인 설정
…sist [SETTING] Gemini PR 리뷰 가이드라인 문서 작성
[FEAT] 디자인 시스템 구축
…flix-23rd into feature/#5-common-layout
[FEAT] 공통 레이아웃 구현 (헤더, 하단 내브바, 아이콘 시스템)
[REFACTOR] 홈페이지 구조 개선 및 상세 페이지 연결
[FEAT] 상세 페이지 UI 구현
yiyoonseo
left a comment
There was a problem hiding this comment.
바쁘신 와중에도 5주차 과제 너무 깔끔하게 잘 해주신 것 같습니다 ! 많이 배워갑니다😍
| audio.currentTime = 0; | ||
| router.push("/home"); | ||
| }, 3000); | ||
| }; |
There was a problem hiding this comment.
3000ms로 시간을 고정해두는 방법도 좋지만 lottie-react에서 제공하는 onComplete 콜백을 사용하면 애니메이션이 끝나는 즉시 페이지를 이동시킬 수 있어 훨씬 자연스러울 것 같습니다!
공식 문서입니다!
https://lottiereact.com
| priority={index === 0} | ||
| /> | ||
| ))} | ||
| <div className="bg-gradient-thumbnail absolute inset-0" /> |
There was a problem hiding this comment.
개별적으로 유틸리티 클래스 만드셔서 그라데이션 구현하신거 너무 좋은거 같아요!
| <Swiper | ||
| modules={[FreeMode]} | ||
| freeMode={{ momentum: true }} | ||
| grabCursor | ||
| slidesPerView="auto" | ||
| spaceBetween={7} | ||
| > | ||
| {items.map(item => ( | ||
| <SwiperSlide key={item.id} style={{ width: "auto" }}> | ||
| <MediaThumbnail item={item} shape={shape} size={size} /> | ||
| </SwiperSlide> | ||
| ))} | ||
| </Swiper> |
There was a problem hiding this comment.
swiper 라이브러리로 영화 포스터 썸네일들을 옆으로 편하게 넘길 수 있어서 너무 효과적인거 같아요
| console.error(`[tmdbClient] ${path}`, error); | ||
| throw error; | ||
| } | ||
| }; |
There was a problem hiding this comment.
홈 화면에 필요한 API를 섹션별 함수로 분리한 점이 좋았습니다.
각 섹션이 어떤 데이터를 사용하는지 함수명만 봐도 이해하기 쉽고, 이후 카테고리 추가나 수정사항이 생겨도 관리하기 편할 것 같아요!
minseo0614
left a comment
There was a problem hiding this comment.
5주차 과제 수고하셨습니다! 코드 리뷰하면서 많이 배울 수 있었습니다
| <div className="relative h-103.75 w-full"> | ||
| {items.map((item, index) => ( | ||
| <Image | ||
| key={item.id} | ||
| src={getTmdbImageUrl(item.poster_path ?? "", "w500")} | ||
| alt={getMediaTitle(item) || "이미지 없음"} | ||
| fill | ||
| sizes="375px" | ||
| className={`object-cover transition-opacity duration-700 ${index === current ? "opacity-100" : "opacity-0"}`} | ||
| priority={index === 0} | ||
| /> | ||
| ))} | ||
| <div className="bg-gradient-thumbnail absolute inset-0" /> |
There was a problem hiding this comment.
Hero 슬라이드를 이미지 src를 갈아끼우는 방식이 아니라 10장을 한 번에 다 렌더해두고 opacity로 전환해서 슬라이드가 깜빡임 없이 부드럽게 페이드되는 부분 너무 좋은 것 같아요!
| import MediaCardCarousel from "@/components/home/MediaCardCarousel"; | ||
| import { getPopularMovies } from "@/lib/apis/home"; | ||
|
|
||
| const PreviewSection = async () => { |
There was a problem hiding this comment.
page.tsx에는 레이아웃만 잡고,각 섹션이 server component로 자기 데이터를 직접 가져가게 만드신 구조 깔끔하네요!!
…iles Feat: svg파일 추가
[REFACTOR] SEO 강화 및 빌드, 코드 개선
[FEAT] 검색 페이지 UI 구현
과제 관련 링크
Research Questions
느낀 점 및 배운 점
오진
협업
Next.js와pnpm으로 진행하게 되어 또 다른 지식과 협업 방식 경험을 얻을 수 있었습니다.Next.js
“use client”를 필요한 부분에만 사용하도록 고려하며 작성하였습니다. 모든 파일이 아니라 경계가 되는 파일에만 사용하며 또한 부모 파일에서 이미 사용하였다면 import한 자식 컴포넌트는 붙이지 않아도 동작한다는 점에 유의하였습니다.API
page.tsx에 여러 API를 한 번에 호출하여 각 섹션에 데이터를 전달하는 구조로 구현했습니다. 팀 내 코드 리뷰를 통해 각 섹션 서버 컴포넌트가 데이터를 직접 가져오게 하며 page.tsx는 레이아웃만 잡는 방식으로 수정하였습니다.기타
유민
협업
렌더링 메커니즘
"use client"를 선언하는 방식으로 설계했습니다. 이러한 과정을 통해 컴포넌트 간의 계층 구조와 데이터 흐름을 더 정교하게 제어하는 방법을 깊이 있게 이해할 수 있었습니다. 이론적으로만 접하던 내용을 직접 구현해보며 빠르게 감을 잡을 수 있었던 것 같습니다.데이터 페칭 전략 및 에러 핸들링
Axios대신 Next.js와 시너지가 좋은 내장fetch를 적극 도입했습니다. 익숙한 방식을 벗어나 새로운 라이브러리 활용법을 고민해 볼 수 있었고, 향후 TanStack Query와의 연동 가능성까지 고려하며 기술적 지평을 넓힐 수 있었습니다. (참고자료1 참고자료2)try-catch를 두지 않고tmdbClient에서 일괄 처리하도록 설계했습니다.res.ok체크로 HTTP 에러를 조기에 감지하고,catch블록에서 로깅 후 에러를 상위로 전파하여 호출부까지 버블업하도록 구현했습니다. 덕분에home.ts,detail.ts등의 개별 API 함수는 비즈니스 로직에만 집중하며 깔끔한 코드를 유지할 수 있었습니다.이미지 최적화
next/image의 활용 방안을 다각도로 검토하여 적용했습니다.remotePatterns를 설정을 통해 허용된 호스트(image.tmdb.org)로부터의 이미지 서빙을 제한하여 보안을 강화했습니다. 이를 통해 외부 도메인의 이미지를 Next.js 고유의 최적화 파이프라인으로 안전하게 처리할 수 있었습니다. (참고자료)협업 과정
컨벤션
코드의 일관성과 효율적인 히스토리 관리를 위해 명확한 코드 및 워크플로우 규칙을 정의했습니다.
#이슈번호 [타입] 작업 내용형식의 커밋 메시지와feature/#이슈번호-작업명브랜치 전략을 통해 작업 이력을 투명하게 관리했습니다.폴더 구조
도메인 중심의 구조로 설계하여 유지보수성을 높였습니다.
next-netflix-23rd/ ├── public/ # 정적 자원 (Font, Image, Lottie 등) │ ├── favicon.svg │ ├── og_image.png │ ├── fonts/ │ ├── images/ │ ├── lotties/ │ └── sounds/ └── src/ ├── app/ # Next.js App Router (Page 및 Layout) │ ├── detail/[mediaType]/[id]/page.tsx │ ├── home/page.tsx │ ├── layout.tsx │ ├── loading.tsx │ ├── not-found.tsx │ ├── page.tsx │ └── globals.css │ ├── components/ # 도메인별 컴포넌트 분리 (common, home) │ ├── common/ │ │ ├── BottomNavbar.tsx │ │ ├── Button.tsx │ │ ├── Header.tsx │ │ └── HomeIndicator.tsx │ └── home/ │ ├── TrendingSection.tsx │ ├── TrendingHero.tsx │ ├── PreviewSection.tsx │ ├── NetflixOriginalsSection.tsx │ ├── AnimationMoviesSection.tsx │ ├── KoreanMoviesSection.tsx │ ├── MediaCardCarousel.tsx │ └── MediaThumbnail.tsx │ ├── lib/ # 비즈니스 로직 (apis, utils) │ ├── apis/ │ │ ├── tmdbClient.ts │ │ ├── home.ts │ │ └── detail.ts │ └── utils/ │ ├── tmdb.ts │ └── cn.ts │ ├── constants/ # 공통 상수 관리 │ ├── tmdb.ts │ └── navItems.ts │ ├── types/ # TypeScript 타입 정의 │ ├── home.ts │ └── detail.ts │ └── assets/ # 프로젝트 내부 사용 에셋 (icons) └── icons/기술 스택 및 선정 이유
next: { revalidate }) 활용을 위해 axios 대신 사용prettier-plugin-tailwindcss로 클래스 순서 정렬simple-import-sort로 import 순서 자동 정렬Swiper라이브러리를 도입하여 부드러우면서도 정확한 사용자 경험을 제공하고자 했습니다.fetchAPI를 활용한 데이터 페칭 구조를 구축했습니다. 이를 통해 외부 라이브러리 의존성을 최소화할 수 있었을 뿐만 아니라, 프레임워크 수준에서 제공하는 서버 측 캐싱, 재검증을 별도 설정 없이 즉시 활용하여 런타임 성능을 높이기 위한 선택이었습니다.pnpm을 채택했습니다. 먼저 전역 저장소에 패키지를 관리하는 콘텐츠 주소 지정형 방식을 활용해, 중복 설치되는 패키지를 제거하고 저장 공간을 획기적으로 아끼고 싶었습니다. 또한, 심링크 기반의 의존성 연결 구조를 통해npm보다 압도적으로 빠른 설치 속도를 확보하고자 했습니다. 결과적으로 환경 세팅에서 발생하는 병목을 없애고 개발에 집중할 수 있는 환경을 만들고자 선택했습니다.소통 방식
당장 이번 주 과제만 생각하면 카톡으로만 소통해도 문제 없지만, 장기적인 협업과 효율적인 정보 공유를 위해 채널을 세분화하여 운영했습니다. 서로 연락 확인이 빨라 개발이 수월하게 진행되었습니다.
[5주차] 제목형식으로 히스토리 관리)업무 분담 및 R&R
단순히 페이지를 나누어 각자 구현하는 방식이 아니라, 전체적인 설계와 기술적 난이도를 사전에 충분히 검토한 뒤 전략적인 R&R을 수립했습니다.
컨벤션, 기술 스택, 소통 방식, PR 및 규칙 등을 함께 정한 후 피그마를 보며 전체적인 서비스 플로우를 먼저 확립했습니다. 어떤 라이브러리가 추가로 필요할지, 어느 기능에 개발 공수가 많이 들지를 미리 논의했습니다.

구현 난이도가 높은 부분 또는 레퍼런스 서치가 필요한 부분에 대해서는 리서치 범위를 정하고, 최종적으로 각자의 강점에 맞춰 아래와 같이 업무를 분배했습니다.
마감 기한 내에 높은 완성도를 확보하기 위해 상대적으로 경험이 더 많은 제가 초기 환경 세팅 및 공통 레이아웃 작업을 선제적으로 완료하여 오진님이 구현에 집중하실 수 있도록 환경을 세팅했습니다.
또한 작업 충돌을 방지하기 위해 도메인을 명확히 분리했습니다. 오진님이 디자인 시스템을 구축하시는 동안, 저는 전체적인 레이아웃 시스템 및 App Router의 뼈대를 구축하여 개발 병목 현상을 제거했습니다.
협업의 본질에 맞게 업무 비중을 고르게 분배하는 데도 초점을 두었습니다. 그리고 코드 리뷰를 통해 끊임없이 더 좋은 구조를 제안하고 고민했던 시간들이 큰 도움이 되었습니다. 다음 주부터는 남은 기능 개발과 서비스 최적화에 집중해서 완성도를 더 높여보려 합니다.
Review Questions
React 18 버전의 변경점
렌더링 방식 자체를 더 유연하고, 끊김없고 대규모 앱에 적합하게 바꿈
서버 컴포넌트와 클라이언트 컴포넌트
서버 컴포넌트
useState,useEffect등)은 직접 사용 불가 → 불필요한 UI 로직을 브라우저에 전송하지 않아도 됨⇒ 서버에서 미리 데이터를 가져와 필요한 마크업을 만들어서 클라이언트에 최소한의 결과와 필요한 상호작용 부분만 남기려고 함
클라이언트 컴포넌트
useState,useEffect,useReducer훅 사용 가능⇒ 사용자 이벤트에 반응, 브라우저 전용 API 사용 등 사용자와의 즉각적인 상호작용 필요 시 사용
lazy loading과 Suspense 컴포넌트
lazy loading
import하여 실제로 렌더링이 필요한 시점에만 컴포넌트 불러옴React.lazy로 감싼 컴포넌트는 반드시Suspense컴포넌트 하위에서 렌더링되어야 함Suspense
lazy로 로딩된 컴포넌트가 준비될 때까지 기다렸다가 로딩 상태 표시fallbackUI를 보여줌(fallback Prop : 컴포넌트가 로딩되는 동안 보여줄 UI(ex: 로딩 스피너, 스켈레톤 등) 설정)
Suspense로 여러lazy컴포넌트를 감싸서 한 번에 로딩 상태 관리 가능Suspense는 코드 분할용이라는 이미지가 강했으나 React 18 이후에는 비동기 UI 경계로 더 중요해짐Automatic batching
ReactDOM.createRoot메서드를 기반으로 렌더링 진행할 경우 모든 state update 작업은 자동으로 batchingConcurrent Rendering
render대신createRoot를 사용하며, 이와 같은 Concurrent Mode를 설정하면startTransition,useTransition,useDeferredValue훅들을 사용하여 개선된 기능들과 동시 처리가 가능해짐