Skip to content

[6주차] Team Ditda 권오진 & 박유민 과제 제출합니다.#11

Open
waldls wants to merge 126 commits into
CEOS-Developers:masterfrom
Hi-Five-Official:master
Open

[6주차] Team Ditda 권오진 & 박유민 과제 제출합니다.#11
waldls wants to merge 126 commits into
CEOS-Developers:masterfrom
Hi-Five-Official:master

Conversation

@waldls

@waldls waldls commented May 8, 2026

Copy link
Copy Markdown

작업 관련 링크


느낀 점 및 배운 점

오진

  • 이번 주차는 검색페이지 및 무한스크롤 기능 추가를 맡아 구현을 진행하였습니다.
    검색페이지 구현 초기 useQuery을 이용하여 검색 API를 호출하였습니다. 그러나 검색페이지 UI 구현을 마친 후, 무한 스크롤 기능을 추가할 때 페이지 단위 데이터를 누적해야 한다는 점을 고려하여 useInfiniteQuery로 구조를 변경하였습니다. 그 과정에서 전용 API인 getNextPageParam, fetchNextPage, hasNextPage에 대해 이해하였습니다.

  • 무한 스크롤 구현 초기 스크롤 이벤트가 발생할 때마다 바로 다음 페이지를 호출하는 방식으로 진행하였습니다. 그러나 해당 방법은 불필요한 API 요청을 과도하게 발생시킬 수 있고 해당 기능에 대해 얘기한 부분과 방향이 달라 코드를 수정하였습니다. onScroll 이벤트에서 scrollTop, clientHeight, scrollHeight을 활용하여 실제로 리스트가 하단에 도착했을 때 렌더링되도록 수정하였습니다. 불필요한 API 요청은 이후 비용적인 문제와 직결될 수 있으므로 개발에 연관되는 요소 또한 고려해야한다는 점을 알 수 있었습니다. (이후 리팩토링 과정에서 수정되었습니다!)

    const isBottom = target.scrollTop + target.clientHeight >= target.scrollHeight;
  • 이번 주차 검색페이지 구현이 예상보다 시간 소요가 컸고 기존에 자주 사용하던 라이브러리와 처리 방식이 아니어서 코드 정리가 잘 되어 있지 않았습니다. 아직 리팩토링 및 코드 디밸롭 과정이 쉽지 않아 작성한 코드를 매번 확인하고 리팩토링을 진행한 유민이에게 고마웠으며, 그로 인해 검색페이지에 집중할 수 있었습니다.

유민

  • 5주차와 동일하게 상호 PR 리뷰를 진행했는데, 서로의 코드 작성 스타일과 새로운 접근 방식을 배우면서 코드 퀄리티를 많이 높일 수 있었습니다. 리뷰 과정에 시간이 더 소요되기도 했지만, 결과적으로는 잠재적인 에러를 미리 잡고 통일감 있는 코드를 작성할 수 있어 오히려 가장 효율적이고 빠른 개발 방식이라고 생각했습니다.
  • 지난 주에는 기능 구현 자체에 더 집중했다면, 6주차 과제를 진행하면서는 공식 문서와 블로그를 리서치하는 습관을 기른 것이 가장 큰 자산이 되었습니다. 특히 무한 스크롤 구현 시 onScrollIntersectionObserver 사이에서 성능과 정확도를 고려해 하나를 선택해야 했고, LCP priority 설정 같은 디테일한 최적화 단계로 들어갈수록 스스로 판단해야 할 지점들이 많아졌습니다. 이 과정에서 구글링을 통해 다양한 사례를 접하고 기술적 근거를 세우는 법을 익힐 수 있었습니다.
  • 로딩 속도 개선을 위해 Suspense와 스켈레톤 UI를 도입하면서, 무조건적인 수치 개선보다 사용자가 체감하는 안정적인 경험이 중요하다는 것을 배웠습니다. 폰트 프리로드, DNS preconnect, 이미지 priority 설정 등 실질적인 사용자 지표를 개선할 수 있는 방법들을 고민하고 적용해 보면서 개발 환경과 실제 사용자 환경 사이의 간극을 줄이는 최적화 전략을 익힐 수 있었습니다. 스켈레톤 UI처럼 디테일한 부분까지 신경 쓰는 프론트엔드의 매력도 다시 한번 느꼈습니다.
  • 이번 주차에 오진 오빠가 무한스크롤 구현에 집중해 준 덕분에 저는 상세 페이지와 전반적인 애플리케이션 최적화 작업에 집중할 수 있었습니다. 고생 많았을 텐데 세심하게 신경 써줘서 정말 고맙다는 말을 전하고 싶습니다. 이번 협업 과제에서 고민했던 내용들이 향후 Ditda 개발할 때 도움이 되었으면 좋겠습니다. 다음 주 과제에서도 해보고 싶었던 기술 스택 + 기술들 미리 도전해 볼 수 있었으면 좋겠어요~~ 덕분에 너무 재밌게 개발했습니다 짱~~ 👍🏻

Research Question

5주차 → 6주차 과제를 진행하면서 중점적으로 고려한 사항과 그 이유를 작성해주세요.

오진

  • 무한 스크롤 구현 당시 단순히 onScroll 이벤트가 발생할 때마다 다음 페이지를 요청하는 방식으로 접근했습니다. 하지만 scroll 이벤트는 사용자가 화면을 움직이는 동안 높은 빈도로 발생하기 때문에 이벤트가 발생할 때마다 API 요청 검사 및 fetchNextPage 를 실행하면 불필요한 연산과 요청이 많아질 수 있다는 점을 알게 되었습니다. 또한 MDN 문서에서도 연산량이 많은 작업에 대해 scroll 이벤트보다 IntersectionObsever 를 추천했다는 점 또한 알 수 있습니다. Document: scroll event

  • IntersectionObsever 는 관찰 대상이 특정 기준에 도달했을 때 콜백이 실행되기 때문에 이미지 lazy 로딩, 무한 스크롤 같은 기능에서 자주 사용된다는 점도 알아갈 수 있었습니다.

유민

5주차에 정립한 컨벤션과 규칙 덕분에 협업 과정이 안정화되었으며, 6주차에는 이를 바탕으로 세션 피드백과 PR 리뷰 내용을 정교하게 반영하는 데 집중했습니다.

  • 상세 페이지에서는 mediaType과 id를 인자로 받아 단일 API 엔드포인트로 효율적인 동적 라우팅을 구현했습니다.

  • 빌드 속도 개선을 위해 Webpack에서 Turbopack으로 마이그레이션하고, 하드코딩된 수치 대신 디자인 시스템 토큰을 전면 적용하여 유지보수 효율성과 UI 계층 구조의 시각적 일관성을 강화했습니다.

  • 동적 메타데이터를 구현해 정보 공유 시 정확도를 높이고, Page Visibility API로 백그라운드 리소스 소모를 방지하는 등 디테일한 부분까지 사용자 편의를 고려했습니다.

    image
  • 수치 기반(예: z-10)의 매직 넘버 사용을 지양하고, 사전에 정의된 디자인 시스템 토큰을 사용하게 함으로써 계층 구조의 일관성을 확보하고 유지보수성을 높였습니다.

  • 기존의 복잡한 명령형 상태 관리를 useSuspenseInfiniteQuerynext/dynamic으로 교체하여, 데이터 로딩 중에는 스켈레톤 UI를 보여주고 완료 시점에 실제 콘텐츠를 매끄럽게 노출함으로써 UX의 명확성을 확보했습니다.

  • Scroll 이벤트 대신 IntersectionObserver를 도입해 메인 스레드 부하를 줄였으며, API Waterfall 제거 및 엔드포인트별 캐시 전략 차별화로 불필요한 네트워크 비용을 절감하고 응답 속도를 개선했습니다.

  • 브라우저별 최적 포맷(AVIF/WebP) 제공, LCP 이미지 우선순위 설정(priority), 폰트 및 DNS preconnect를 적용해 시각적 완성도를 높이고 레이아웃 시프트를 원천 차단했습니다.

waldls and others added 30 commits April 28, 2026 13:21
[DEPLOY] Vercel 프리뷰 및 master 자동 배포 파이프라인 설정
…sist

[SETTING] Gemini PR 리뷰 가이드라인 문서 작성
[FEAT] 공통 레이아웃 구현 (헤더, 하단 내브바, 아이콘 시스템)
Comment thread src/types/search.ts
profile_path: string | null;
}

export type TmdbSearchResult = TmdbSearchMovie | TmdbSearchTv | TmdbSearchPerson;

@YJ0623 YJ0623 May 9, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

타입 분리 및 상태 전달을 타입 안정성이 보장되도록 깔끔하게 잘 구현하신 것으로 보입니다. 저희 팀은 tmdb에서 search요청을 multi로 안보내고 movie에다가 media_type: 'movie'만 받아오는 방식으로 구현했는데요, 저도 이렇게 구현했다면 더 좋았을 것 같네요.

Comment thread src/app/home/page.tsx
return (
<div className="relative">
<div className="sticky top-0 z-10">
<div className="z-sticky sticky top-0">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

피드백에 있었던 z-index 토큰 지정도 잘 반영해주셨네요. 나중에 z-index를 사용하는 요소가 많아지면 저희도 사용 도입을 검토해보도록 하겠습니다..!

Comment thread src/app/search/page.tsx

const TopSearchContent = dynamic(() => import("@/components/search/TopSearchContent"), {
ssr: false,
loading: () => <SearchResultSkeleton />,

@YJ0623 YJ0623 May 9, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 TopSearchContent 컴포넌트 내부에 useSuspenseInfiniteQuery가 잘 작성되어 있는데요, 부모 컴포넌트에서 이를 불러올 때 dynamic(..., { ssr: false })를 사용하게 되면, 클라이언트 컴포넌트화가 되어버립니다. 이렇게 되면 의도하신 방향으로 보이는, 스켈레톤을 사용하는 의도가 조금 옅어질 수도 있어요.
현재 코드의 양이 많고 의존성도 생겨서 수정은 어렵겠지만, 다음에는
<Suspense fallback={<SearchResultSKeleton/>} 형식으로 코드를 작성해보시는 걸 추천드립니다.

};

start();
if (document.visibilityState === "visible") start();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

되게 디테일한 부분을 잘 캐치하신 것 같아요. 화면이 보이지 않을 때를 고려하여 document.visibilityState 옵션을 사용하는 게 사용자는 체감이 되지 않더라도 리소스상으로라도 조금씩 이득을 보게 해주는, 좋은 디테일이라고 생각합니다. 좋은 코드 잘 봤습니다 감사합니다.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[id]만이 아니라 [mediaType]도 포함하신게 UX에 좋은 것 같아 새롭게 배우게 되었습니다! 또 저는 상세페이지는 비교적 코드가 짧다고 느껴 따로 컴포넌트 분리를 하지 않았는데 이렇게 분리해서 관리하니까 깔끔하네요

Comment thread src/types/detail.ts

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

옵셔널이 아니라 | null 로 처리하신 점이 인상깊었습니다. 저는 옵셔널을 사용했는데 이 방식이 더 안전한 방식인 것 같아요!
궁금한 점은 original_@@(title, name 등) 값들도 string이 필수로 되어있는데 혹시 언어 상태나 데이터 등록 상태에 따라 값이 비어있을 수도 있는 점을 고려해서 마찬가지로 옵셔널 또는 | null 처리를 하면 어떤지 생각이 들었습니다.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 폰트 파일을 src에 넣어 관리했는데 이렇게 하면 빌드시 파일 명에 고유 해시 값이 붙어서 브라우저 캐시 관리 측면에서 유리한 점이 있다고 해서 위치 변경을 고려해보셔도 좋을 것 같습니다!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

폰트와 마찬가지로 저희는 lottie 파일을 src에서 관리했는데요, json 파일이기 때문에 타입 안정성, 네트워크 요청 최적화 등의 측면에서 public보다는 src에서 관리하는 게 이점이 많아서 이동을 검토해보셔도 좋을 것 같습니다!

Comment on lines +15 to +17
if (!res.ok) {
throw new Error(`TMDB API error: ${res.status} ${res.statusText}`);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API 실패 시 에러를 던지도록 처리한 점이 좋은 것 같습니다!

다만, 현재 /home 페이지가 TrendingSection, PreviewSection, NetflixOriginalsSection 등 각각 서버 컴포넌트에서 TMDB 데이터를 가져오는 구조라, 특정 섹션의 API 요청 하나가 실패하면 가장 가까운 error boundary까지 에러가 전파될 수 있어 보입니다.

Next 공식 문서에서도 uncaught exception은 가장 가까운 error boundary로 bubble up된다고 설명하고 있어서, 홈 화면처럼 여러 섹션이 독립적으로 구성된 페이지에서는 섹션 단위 fallback을 둘지, 아니면 라우트 전체 error UI로 처리할지 기준을 한 번 고민해보시면 좋을 것 같습니다!

🔗 https://nextjs.org/docs/app/getting-started/error-handling#nested-error-boundaries

@a-00-a a-00-a left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6주차 과제하시느라 수고하셨습니당!!

Webpack 강제 옵션 제거와 next.config.ts 정리를 함께 진행해서
Next.js 최신 빌드 체계로의 마이그레이션이 자연스럽게 이루어진 것 같습니다.
관련 설정들이 일관되게 정리되어 있어 좋아요!

Comment thread package.json
Comment on lines +6 to +8
"dev": "next dev",
"build": "next build",
"start": "next start",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--webpack 플래그를 제거하여 기본 번들러 체계로 전환한 점 좋습니다.

Comment thread next.config.ts
Comment on lines +13 to +20
turbopack: {
rules: {
"*.svg": {
loaders: [{ loader: "@svgr/webpack", options: { dimensions: false } }],
as: "*.js",
},
},
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turbopack/최신 Next 설정과 충돌 가능성 있는 설정도 함께 정리되어 있어서 마이그레이션 방향이 일관적인 것 같습니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants