[5주차] ORLO 김홍엽 & 오유진 과제 제출합니다.#8
Conversation
chore: 프로젝트 초기 세팅
chore: 이슈 및 PR 템플릿 추가
chore: Husky 설정
chore: 개인 레포 미러링 배포 워크플로우 추가
chore: coderabbit 설정
feature: tmdb api 연동 및 환경변수 설정
Feature/08 common layout
Feature/09 movie service
Feature/12 featured content
Feature/13 movie previews
Feature/16 landing page
chore: coderabbit dev에도 적용
Fix/icon path issue
Feature/20 main page movies per section
release: 메인 페이지 1차 배포
Feature/23 figma layout
release: 메인 페이지 v2
| @@ -0,0 +1,10 @@ | |||
| import axios from 'axios'; | |||
|
|
|||
| const instance = axios.create({ | |||
There was a problem hiding this comment.
https://pestudent.tistory.com/96
Next.js에서는 axios 대신 fetch 메서드를 통해 api 연동을 구현하는 것이 권장 사항입니다!
| const isActive = pathname === item.href; | ||
|
|
||
| return ( | ||
| <Link |
There was a problem hiding this comment.
Next.js의 이점인 Link를 usePathname과 묶어 사용하신 점 좋습니다. SEO측면에서 용이하다고 합니다.
| <section className="overflow-hidden py-3"> | ||
| <h2 className="text-heading2 mb-3 px-4 text-white">{title}</h2> | ||
|
|
||
| <div className="no-scrollbar flex gap-[7px] overflow-x-auto px-3"> |
| <div className="flex flex-col"> | ||
| <Featured /> | ||
| <Previews /> | ||
| <MovieRow title="Continue Watching for You" fetchKey="getContinueWatching" variant="tall" /> |
There was a problem hiding this comment.
const로 title, fetchKey, variant?를 관리해서 map으로 MovieRow를 뿌렸다면 어땠을까 싶어요!
| /> | ||
|
|
||
| <div className="relative h-[55vh] w-full"> | ||
| <Image |
There was a problem hiding this comment.
이런 컴포넌트는 w-full이기 때문에 뷰포트 크기가 작아져도(세로가 줄어도) 큰 영향이 없지만, 여러 컴포넌트를 동시에 vh로 관리하면서 Image 속성을 fill로 설정하면 위험할 수도 있습니다!
| animationData={netflixIntro} | ||
| loop={false} | ||
| onComplete={() => { | ||
| sessionStorage.setItem('splashShown', 'true'); |
There was a problem hiding this comment.
sessionstorage 사용해서 새로고침해도 로고가 안뜨게 하는 부분이 너무 좋은거 같아요!
| <MovieRow title="TV Thrillers & Mysteries" fetchKey="getThrillerMovies" /> | ||
| <MovieRow title="US TV Shows" fetchKey="getComedyMovies" /> | ||
| </div> | ||
| </SplashScreen> |
There was a problem hiding this comment.
스플래쉬를 별도의 페이지가 아니라 status에 따른 조건부 랜더링으로 표현하신게 너무 좋은거 같아요!
| return ( | ||
| <html lang="ko" className="font-sans"> | ||
| <body className="bg-black antialiased"> | ||
| <Layout>{children}</Layout> |
| <div className="relative h-[415px] w-full"> | ||
| <Image | ||
| src={`https://image.tmdb.org/t/p/original${movie.poster_path}`} | ||
| alt={movie.title || movie.name || ''} | ||
| fill | ||
| sizes="100vw" | ||
| className="object-cover" | ||
| priority | ||
| /> | ||
| <div className="absolute inset-0 bg-linear-to-t from-black via-transparent to-transparent" /> |
There was a problem hiding this comment.
배너 이미지 위에 어두운 그라데이션 살짝 깔아서 어떤 포스터가 와도 하단 텍스트가 잘 보이게 처리하신 부분 너무 좋은 것 같아요!
| import { movieService } from '@/api/movieService'; | ||
| import { Movie } from '@/types/movie'; | ||
|
|
||
| type MovieServiceKey = keyof typeof movieService; |
There was a problem hiding this comment.
MovieRow가 fetchKey 문자열만 받아서 어떤 카테고리든 동작하게 만드신 거, 새 row 추가할 때 한 줄로 끝나서 좋은 것 같아요!
| function getInitialStatus(): 'splash' | 'done' { | ||
| if (typeof window === 'undefined') return 'splash'; | ||
| return sessionStorage.getItem('splashShown') ? 'done' : 'splash'; | ||
| } | ||
|
|
||
| export default function SplashScreen({ children }: SplashScreenProps) { | ||
| const [status, setStatus] = useState<'splash' | 'done'>(getInitialStatus); |
There was a problem hiding this comment.
splash 띄울지 말지를 useEffect 안이 아니라 useState 초기값에서 바로 결정하면 첫 진입 때 깜빡임 없이 분기되어서 좋은 것 같아요!
| export interface MovieResponse { | ||
| page: number; | ||
| results: Movie[]; | ||
| total_pages: number; | ||
| total_results: number; | ||
| } |
There was a problem hiding this comment.
total_pages 필드를 MovieResponse 인터페이스에 명시해둔 점이 좋았습니다. 무한 스크롤 구현 시 page < total_pages 조건 등으로 다음 페이지 요청 가능 여부를 판단할 수 있어, 불필요한 API 호출을 방지하는 데 도움이 될 것 같습니다.

배포 링크
🔗 https://ceos-week5-next-netflix-23rd.vercel.app/
협업 과정
1. Git Branch 전략
maindevfeature/*main은 배포 가능한 상태만 유지하며, 실제 개발 작업은dev브랜치를 기준으로 이루어졌습니다.dev에서 분기한feature/*브랜치에서 작업한 뒤, PR을 통해 다시dev로 머지하는 방식으로 진행했습니다.2. 작업 흐름
feature/*브랜치 생성dev브랜치를 대상으로 Pull Request 작성dev에 반영3. 패키지 매니저 선정과 그 이유
pnpm
4. 디렉토리 구조
5. 업무 분담
김홍엽
오유진
6. 소통 방식
프로젝트 초기에 비대면 회의를 통해 전체적인 방향성을 정립했습니다.업무 분담뿐만 아니라 패키지 매니저, 커밋 메시지 컨벤션, PR 컨벤션, Issue/Branch 관리 규칙 등 프로젝트 세팅 전반에 대해 충분히 논의한 뒤 작업에 착수했습니다.
작업 기간 동안에는 카카오톡을 활용해 진행 상황을 주기적으로 공유했습니다. 각자의 작업 현황을 수시로 공유하며 전체 일정에 차질이 없도록 조율했습니다.
데모데이 프로젝트에 앞서, 해보고 싶은 것들과 소통 방식을 시도해보며 추후에 취사선택할 수 있도록 많이 해보려고 했습니다.
또한 이번 과제가 데모데이 프로젝트의 사전 단계인 만큼, 다양한 협업 방식과 도구를 적극적으로 시도해보는 데 의의를 두었습니다. 이슈 템플릿, PR 템플릿, 커밋 컨벤션, CodeRabbit 자동 리뷰, Husky 기반 Git Hooks 등 여러 협업 장치를 도입했습니다.
Review Questions
React 18 버전의 변경점
1. Concurrent Rendering
Before
After (React 18)
2. Automatic Batching
Before
After (React 18)
createRoot를 사용해야 적용되며, 기존ReactDOM.render를 그대로 쓰면 이전과 동일하게 동작3. Lazy Loading과 Suspense 컴포넌트
Before
React.lazy로 감싼 컴포넌트가 실제 렌더링이 시도되는 시점에 동적으로 코드가 로드되고, 그동안 Suspense의 fallback이 표시되며 잘 동작했음.renderToString,renderToNodeStream)가 모두 동기적으로 동작해서, Suspense의 핵심 동작인 Promise를 기다리는 것이 불가능했음.React.lazy를 쓰고, SSR이 필요하면@loadable/component같은 서드파티 라이브러리를 쓰는 식으로 이원화되어 있었음.After (React 18)
renderToPipeableStream을 도입하면서 SSR에서 Suspense를 제대로 지원하게 되어,React.lazy가 별도의 라이브러리 없이 SSR과 자연스럽게 통합됨.4. 서버 컴포넌트와 클라이언트 컴포넌트
등장 배경
useEffect기반이라 waterfall 문제 발생서버 컴포넌트
클라이언트 컴포넌트
"use client"선언