Transcript

00:00:00(경쾌한 음악) - 안녕하세요, 여러분.
00:00:06제 이름은 오로라입니다.
00:00:07저는 노르웨이 출신 웹 개발자입니다.
00:00:09저는 Crane Consulting에서 컨설턴트로 일하고 있으며,
00:00:12현재 컨설팅 프로젝트에서 Next.js 앱 라우터를 적극적으로 활용하여 개발하고 있습니다.
00:00:16오늘 저는 최신 Next.js에서 확장성과 성능을 보장하는 데 도움이 될 구성,
00:00:21캐싱,
00:00:21아키텍처 관련 패턴들을 알려드릴 것입니다.
00:00:24먼저 이 강연의 가장 기본적인 개념인 정적 및 동적 렌더링에 대해 다시 설명해 드리겠습니다.
00:00:30Next.js 앱 라우터에서 이 두 가지를 모두 접하게 됩니다.
00:00:33정적 렌더링은 미리 렌더링된 콘텐츠를 캐시하고 전 세계적으로 배포하여 사용자가 더 빠르게 접근할 수 있도록 하므로 더 빠른 웹사이트를 구축할 수 있게 해줍니다.
00:00:42예를 들어, Next.js Conf 웹사이트가 있습니다.
00:00:46정적 렌더링은 사용자 요청마다 콘텐츠를 생성할 필요가 없으므로 서버 부하를 줄여줍니다.
00:00:51미리 렌더링된 콘텐츠는 페이지 로드 시 이미 제공되므로 검색 엔진 크롤러가 색인화하기에도 더 쉽습니다.
00:00:58반면에 동적 렌더링은 애플리케이션이 실시간 또는 자주 업데이트되는 데이터를 표시할 수 있도록 합니다.
00:01:05또한 대시보드나 사용자 프로필과 같은 개인화된 콘텐츠를 제공할 수 있게 해줍니다.
00:01:09예를 들어, Vercel 대시보드가 있습니다.
00:01:12동적 렌더링을 사용하면 요청 시에만 알 수 있는 정보에 접근할 수 있습니다.
00:01:16이 경우, 누가 대시보드에 접근하는지, 즉 저와 같은 사용자 정보 말이죠.
00:01:20페이지를 동적으로 렌더링하게 만드는 특정 API들이 있습니다.
00:01:25페이지에 전달되는 `params` 및 `search params` props 또는 그에 상응하는 훅을 사용하면 동적 렌더링이 발생합니다.
00:01:32하지만 `params`를 사용하면 일반적인 `static params`를 통해 미리 렌더링될 페이지 세트를 미리 정의할 수 있으며,
00:01:37사용자가 페이지를 생성하는 동안 해당 페이지들을 캐시할 수도 있습니다.
00:01:40또한, 들어오는 요청의 쿠키와 헤더를 읽으면 페이지가 동적 렌더링으로 전환됩니다.
00:01:46하지만 `params`와 달리,
00:01:48헤더나 쿠키를 사용하여 무엇이든 캐시하거나 미리 렌더링하려고 하면 빌드 중에 오류가 발생합니다.
00:01:52해당 정보는 미리 알 수 없기 때문입니다..
00:01:56마지막으로,
00:01:56`fetch`를 `data cache configuration no store`와 함께 사용하면 동적 렌더링이 강제됩니다.
00:02:00이것들이 몇 가지 예시이며,
00:02:02동적 렌더링을 유발할 수 있는 API는 더 있지만,
00:02:04이들이 가장 흔하게 접하는 것들입니다.
00:02:06이전 Next 버전에서는 페이지가 완전히 정적이거나 완전히 동적으로 렌더링되었습니다.
00:02:13페이지에 단 하나의 동적 API만 있어도 전체 페이지가 동적 렌더링으로 전환됩니다.
00:02:17예를 들어, 쿠키 값에 대한 간단한 인증 확인을 하는 경우입니다.
00:02:20서스펜스를 사용하는 React 서버 컴포넌트를 활용하면 개인화된 환영 배너나 추천과 같은 동적 콘텐츠를 준비되는 대로 스트리밍하고,
00:02:28뉴스레터와 같은 정적 콘텐츠를 보여주는 동안 서스펜스로 대체 콘텐츠만 제공할 수 있습니다.
00:02:34하지만 기능 제품과 같이 여러 비동기 컴포넌트를 동적 페이지에 추가하면,
00:02:40이들은 동적 API에 의존하지 않더라도 요청 시에 실행됩니다.
00:02:45따라서 초기 페이지 로드를 차단하지 않기 위해,
00:02:48해당 컴포넌트들도 서스펜드하고 스트리밍해야 했으며,
00:02:51추가 작업을 하고 스켈레톤을 만들고 레이아웃 이동과 같은 문제에 대해 걱정해야 했습니다.
00:02:56하지만 페이지는 종종 정적 및 동적 콘텐츠가 혼합되어 있습니다.
00:03:01예를 들어, 사용자 정보에 의존하면서도 대부분 정적 데이터를 포함하는 전자상거래 앱이 그렇습니다.
00:03:07정적 또는 동적 중 하나를 강제로 선택해야 하는 것은 서버에서 거의 또는 전혀 변경되지 않는 콘텐츠에 대해 많은 중복 처리를 유발하며 성능에 최적화되지 않습니다.
00:03:19이 문제를 해결하기 위해 작년 Next.js Conf에서 `use cache` 디렉티브가 발표되었습니다.
00:03:26그리고 올해, 기조연설에서 보셨듯이, Next.js 16에서 사용할 수 있게 되었습니다.
00:03:30`use cache`를 사용하면 페이지가 더 이상 정적 또는 동적 렌더링 중 하나로 강제되지 않습니다.
00:03:36두 가지 모두 가능합니다.
00:03:37그리고 Next.js는 더 이상 `params`와 같은 요소에 접근하는지 여부에 따라 페이지가 무엇인지 추측할 필요가 없습니다.
00:03:43모든 것은 기본적으로 동적이며, `use cache`를 통해 명시적으로 캐싱을 선택할 수 있습니다.
00:03:47`use cache`는 구성 가능한 캐싱을 가능하게 합니다.
00:03:51페이지, React 컴포넌트 또는 함수를 캐시 가능으로 표시할 수 있습니다.
00:03:55여기서 우리는 기능 제품 컴포넌트를 실제로 캐시할 수 있습니다.
00:03:59요청 및 처리가 필요 없고 동적 API를 사용하지 않기 때문입니다..
00:04:03그리고 이러한 캐시된 세그먼트는 부분 사전 렌더링을 통해 정적 셸의 일부로 미리 렌더링되고 포함될 수 있습니다.
00:04:09즉, 기능 제품은 이제 페이지 로드 시 사용할 수 있으며 스트리밍할 필요가 없습니다..
00:04:14이제 이 중요한 배경 지식을 바탕으로 데모를 진행해 보겠습니다.
00:04:19Next.js 앱에서 흔히 발생하는 일반적인 문제들을 가진 코드베이스를 개선하는 것입니다.
00:04:24여기에는 깊은 prop 드릴링으로 인한 기능 유지보수 및 리팩토링의 어려움,
00:04:28불필요한 클라이언트 측 JavaScript 및 여러 책임을 가진 대규모 컴포넌트,
00:04:32그리고 정적 렌더링 부족으로 인한 추가 서버 비용 및 성능 저하가 포함됩니다.
00:04:36네, 그럼 시작하겠습니다.
00:04:37잠시만요.
00:04:50좋습니다.
00:04:54이것은 매우 간단한 애플리케이션입니다.
00:04:56전자상거래 플랫폼에서 영감을 받았습니다.
00:04:59여기서 초기 데모를 보여드리겠습니다.
00:05:01이 페이지를 로드할 수 있습니다.
00:05:03이 기능 제품과 같은 콘텐츠가 있습니다.
00:05:06기능 카테고리, 다양한 제품 데이터가 있습니다.
00:05:09여기에는 플랫폼의 모든 제품을 보고 페이지를 넘길 수 있는 '모두 둘러보기' 페이지도 있습니다.
00:05:20그리고 여기에는 정적인 '소개' 페이지가 있습니다.
00:05:24사용자로 로그인할 수도 있습니다.
00:05:27그러면 제 사용자 계정으로 로그인됩니다.
00:05:30그리고 여기 대시보드에서 개인화된 콘텐츠를 받을 수 있습니다.
00:05:33예를 들어, 추천 제품이나 개인화된 할인 정보 같은 것들입니다.
00:05:38여기 보시면, 꽤 잘 섞여 있습니다.
00:05:42아, 그리고 한 가지 더 보여드리는 것을 잊었네요.
00:05:45가장 중요한 제품 페이지입니다.
00:05:47여기서도 제품 정보를 볼 수 있고, 원하면 사용자 계정에 저장할 수 있습니다.
00:05:52따라서 이 앱에는 사용자 의존적인 기능들 때문에 정적 및 동적 콘텐츠가 꽤 잘 혼합되어 있다는 것을 알 수 있습니다.
00:05:59코드도 한번 살펴보겠습니다. 여기 있습니다.
00:06:05저는 Next.js 16에서 앱 라우터를 사용하고 있습니다, 물론이죠.
00:06:08소개 페이지, 전체 페이지, 제품 페이지와 같은 모든 다른 페이지들이 있습니다.
00:06:13또한 앱 폴더를 깔끔하게 유지하기 위해 기능 슬라이싱을 사용하고 있습니다.
00:06:17Prisma를 사용하여 데이터베이스와 통신하는 다양한 컴포넌트와 쿼리가 있습니다.
00:06:23네, 그리고 이 모든 것을 의도적으로 느리게 만들었습니다.
00:06:25그래서 이렇게 긴 로딩 단계가 있는 것입니다. 무슨 일이 일어나는지 더 쉽게 볼 수 있도록 말이죠.
00:06:31그래서 이 애플리케이션에서 실제로 해결하고자 했던 일반적인 문제들은 prop 드릴링으로 인한 기능 유지보수 및 리팩토링의 어려움,
00:06:39과도한 클라이언트 측 JavaScript,
00:06:42그리고 정적 렌더링 부족으로 인한 추가 서버 비용 및 성능 저하였습니다.
00:06:47따라서 이 데모의 목표는 기본적으로 구성,
00:06:49캐싱,
00:06:50아키텍처에 관한 몇 가지 스마트한 패턴을 사용하여 이 앱을 개선하고,
00:06:54이러한 일반적인 문제들을 해결하여 더 빠르고 확장 가능하며 유지보수하기 쉽게 만드는 것입니다.
00:07:01그럼 시작하겠습니다.
00:07:02우리가 해결하고 싶은 첫 번째 문제는 사실 prop 드릴링과 관련이 있습니다.
00:07:05그것은 여기 페이지에 있습니다.
00:07:10여기 보시면, 상단에 `loggedIn` 변수가 있습니다.
00:07:15그리고 제가 이것을 몇몇 컴포넌트에 전달하고 있는 것을 볼 수 있습니다.
00:07:17실제로 이 개인화 배너까지 여러 단계를 거쳐 전달되고 있습니다.
00:07:20따라서 환영 배너에 항상 이 `loggedIn` 의존성이 있기 때문에 여기서 재사용하기가 어려워질 것입니다.
00:07:28따라서 서버 컴포넌트에서는 데이터 페칭을 이를 사용하는 컴포넌트로 내리고,
00:07:33프라미스를 트리 깊숙이 해결하는 것이 가장 좋은 방법입니다.
00:07:37그리고 인증을 받기 위해 `fetch` 또는 React 캐시와 같은 것을 사용하는 한,
00:07:42이 호출을 여러 번 중복해도 되며,
00:07:45컴포넌트 내부 어디에서든 원하는 대로 재사용할 수 있습니다.
00:07:48따라서 재사용하는 것이 전혀 문제가 되지 않습니다.
00:07:51이제 이것을 개인화 섹션으로 옮길 수 있습니다.
00:07:54그리고 더 이상 이 prop가 필요하지 않을 것입니다.
00:07:57그리고 바로 여기에 넣으면 됩니다. 앗.
00:08:01그리고 더 이상 이것을 전달할 필요가 없습니다.
00:08:04그리고 이제 이 비동기 호출을 개인화 섹션으로 옮겼으므로, 더 이상 페이지를 차단하지 않습니다.
00:08:09여기서 간단한 서스펜스로 이것을 서스펜드할 수 있습니다.
00:08:13그리고 이 대체 콘텐츠는 필요하지 않을 것입니다.
00:08:16환영 배너의 경우에도 마찬가지로 할 생각입니다.
00:08:22하지만 여기서 `loggedIn` 변수나 값을 사용하려고 하면 작동하지 않습니다, 그렇죠?
00:08:27이것은 클라이언트 컴포넌트이기 때문입니다.
00:08:29그래서 다른 방식으로 해결해야 합니다.
00:08:30그리고 이것을 해결하기 위해 꽤 스마트한 패턴을 사용할 것입니다.
00:08:33실제로 레이아웃으로 가서 여기 모든 것을 `auth provider`로 감쌀 것입니다.
00:08:39그래서 제 전체 앱을 이것으로 감싸고 이 `loggedIn` 변수를 가져올 것입니다.
00:08:45그리고 저는 분명히 전체 루트 레이아웃을 차단하고 싶지 않습니다.
00:08:48여기서 `await`를 제거하겠습니다.
00:08:50그리고 이것을 프라미스로 이 `auth provider`에 전달하겠습니다.
00:08:55그리고 이것은 그 프라미스를 포함할 수 있습니다.
00:08:57우리가 읽을 준비가 될 때까지 그냥 거기에 있을 수 있습니다.
00:09:01이제 설정이 완료되었습니다.
00:09:03그것은 우리가 먼저 이 prop를 제거할 수 있다는 것을 의미합니다.
00:09:09그리고 개인화 배너로 내려가는 이 prop 드릴링도 제거할 것입니다.
00:09:12그리고 여기에서도 prop 드릴링 또는 시그니처를 제거할 것입니다.
00:09:16그리고 이제 이 `auth provider`를 사용하여 방금 생성한 `use auth`와 함께 개인화 배너 내부에서 이 `loggedIn` 값을 로컬로 가져올 수 있습니다.
00:09:26그리고 `use`로 읽을 수 있습니다.
00:09:28따라서 이것은 실제로 해결되는 동안 서스펜드해야 하는 방식으로 작동할 것입니다.
00:09:33그래서 이제 그 작은 데이터 페치를 개인화 배너 내부에 함께 배치했습니다.
00:09:37그리고 그 prop들을 여기저기 전달할 필요가 없습니다.
00:09:40그리고 이것이 해결되는 동안, 이것도 대체 콘텐츠와 함께 서스펜드하겠습니다.
00:09:44그리고 이상한 누적 이동을 피하기 위해 여기에 일반 배너를 만들겠습니다.
00:09:51그리고 마지막으로, 이것도 제거하겠습니다.
00:09:53이제 이 환영 배너는 구성 가능합니다.
00:09:58재사용 가능합니다.
00:09:59홈페이지에 이상한 prop나 의존성이 없습니다.
00:10:02그리고 이것을 이렇게 쉽게 재사용할 수 있으므로, 여기 이 브라우저 페이지에도 추가해 보겠습니다.
00:10:09여기에 있을 것입니다..
00:10:11그리고 어떤 의존성 없이도 여기서 바로 사용할 수 있습니다.
00:10:15따라서 이러한 패턴들을 통해 React Cache,
00:10:19React Use를 활용하여 좋은 컴포넌트 아키텍처를 유지하고,
00:10:25컴포넌트를 더 유용하고 구성 가능하게 만들 수 있습니다.
00:10:30좋습니다.
00:10:31다음으로 흔한 문제인 과도한 클라이언트 측 JavaScript와 여러 책임을 가진 대규모 컴포넌트를 해결해 봅시다.
00:10:40사실, 그것도 여기 '모두' 페이지에 있습니다.
00:10:43그리고 다시, 이 환영 배너를 작업해야 합니다.
00:10:46현재 클라이언트 컴포넌트입니다.
00:10:48클라이언트 컴포넌트인 이유는 여기에 이 아주 간단한 '닫기' 상태가 있기 때문입니다.
00:10:53이것을 클릭할 수 있습니다.
00:10:54좋은 UI 상호작용입니다.
00:10:56괜찮습니다.
00:10:57하지만 좋지 않은 점은,
00:10:58그 때문에 이 전체 컴포넌트를 클라이언트 측 컴포넌트,
00:11:01즉 클라이언트 컴포넌트로 변환한다는 것입니다.
00:11:04그리고 심지어 `swr`을 사용하여 클라이언트 측에서 데이터를 가져옵니다.
00:11:07이제 여기에 이 API 레이어가 있습니다.
00:11:08더 이상 데이터에 대한 타입 안전성이 없습니다.
00:11:11네, 이것은 필요하지 않습니다.
00:11:12그리고 UI 로직과 데이터를 섞고 있기 때문에 여기서 관심사의 분리도 깨뜨리고 있습니다.
00:11:18그럼 이 문제를 해결하기 위해 또 다른 스마트한 패턴을 사용해 봅시다.
00:11:21도넛 패턴이라고 불립니다.
00:11:23기본적으로 제가 할 일은 이것을 클라이언트 측 래퍼로 추출하는 것입니다.
00:11:27새로운 컴포넌트를 만들어 봅시다.
00:11:29그리고 `banner container`라고 부르겠습니다.
00:11:32그리고 이것은 `use client` 디렉티브와 함께 우리의 상호작용 로직을 포함할 것입니다.
00:11:37시그니처를 만들 수 있습니다.
00:11:38방금 전에 가지고 있던 모든 것을 붙여넣을 수 있습니다.
00:11:42그리고 이 배너들을 사용하는 대신, 여기에 `children`이라는 prop를 슬롯으로 넣을 것입니다.
00:11:48이것이 도넛 패턴이라고 불리는 이유입니다.
00:11:50우리는 서버 렌더링 콘텐츠 주변에 이 래퍼 UI 로직을 만들고 있는 것입니다.
00:11:54또는 서버 렌더링 콘텐츠일 수도 있습니다..
00:11:56그리고 이제 이 클라이언트 측 의존성이 없으므로, `use client`를 제거할 수 있습니다.
00:12:01대신 여기에 `isAuth` 비동기 함수를 사용할 수 있습니다.
00:12:06이것을 비동기 서버 컴포넌트로 만들 수 있습니다.
00:12:09클라이언트 측 페칭을 서버 측 페칭으로 대체할 수도 있습니다.
00:12:11그럼 바로 여기서 할인 데이터를 직접 가져오겠습니다.
00:12:16할인 데이터.
00:12:18그리고 이전처럼 타입 안전성과 함께 우리의 일반적인 멘탈 모델을 활용할 것입니다.
00:12:24그리고 그것은 제가 어쨌든 작업하고 싶지 않은 이 API 레이어를 삭제할 수도 있다는 의미입니다.
00:12:29마지막으로 `isLoading`의 경우,
00:12:31서버 렌더링 콘텐츠를 포함하는 도넛 패턴 배너 컨테이너와 함께 새로운 환영 배너를 여기서 내보낼 수 있습니다.
00:12:38그리고 그것은 더 이상 이 `isLoading`이 필요 없다는 의미입니다.
00:12:40그래서 우리는 기본적으로 이 전체를 서버 컴포넌트로 리팩토링하고 UI 로직 포인트를 추출했습니다.
00:12:46그런데 저게 뭐죠?
00:12:48다른 오류가 있는 것 같습니다.
00:12:51이것은 사실 Motion 때문입니다.
00:12:53Motion을 사용하세요.
00:12:54정말 훌륭한 애니메이션 라이브러리이지만, `useClient` 디렉티브가 필요합니다.
00:12:59그리고 다시 말하지만, 애니메이션 때문에 이것을 `useClient`로 만들 필요는 없습니다.
00:13:03우리는 다시 도넛 패턴 래퍼를 만들고 이 애니메이션들을 위한 래퍼를 추출할 수 있습니다.
00:13:10그리고 그것은 여기서 어떤 것도 클라이언트 측으로 변환할 필요가 없다는 의미입니다.
00:13:14그리고 아마도 제가 여기서 뭔가를 놓치고 있는 것 같습니다.
00:13:17네.
00:13:18됐습니다.
00:13:21이제 여기 모든 것이 서버로 변환되었습니다.
00:13:23우리는 동일한 상호작용을 가지고 있습니다.
00:13:24여전히 상호작용 로직이 있지만, 이제 데이터를 가져오는 한 가지 방법이 있습니다.
00:13:29그리고 클라이언트 측 JS가 훨씬 줄었습니다.
00:13:31사실, 저는 이 UI 경계 헬퍼에 이 도넛 패턴을 직접 사용하고 있습니다. 이렇게 생겼습니다.
00:13:42보이시나요?
00:13:43이것이 다시 제가 무슨 의미인지 보여주는 것이죠?
00:13:45도넛 패턴을 사용하면 서버 컴포넌트 주변에 클라이언트 컴포넌트가 있습니다.
00:13:49다른 많은 컴포넌트들도 이 UI 헬퍼로 표시했습니다.
00:13:53여기에도 더 많은 서버 컴포넌트가 있습니다.
00:13:56이제 이것에 꽤 익숙해졌으니, 그것들도 개선해 봅시다.
00:14:01푸터에 있습니다.
00:14:04이 카테고리들... 제 말은, 자체 데이터를 가져오는 이 멋진 컴포넌트가 있습니다.
00:14:08그리고 혹시 너무 길어질까 봐 이 '더 보기' 기능을 추가하고 싶었습니다.
00:14:14그리고 도넛 패턴을 사용하면 여기에 `showMore` 컴포넌트를 감쌀 수 있습니다.
00:14:20그리고 이것이 제 UI 로직을 포함할 것입니다.
00:14:23그리고 이렇게 생겼습니다, 그렇죠?
00:14:27꽤 멋지죠.
00:14:28그리고 이것은 이제 클라이언트 로직을 포함하여 상태를 사용할 수 있게 해줍니다.
00:14:33우리는 `children` 개수와 배열을 사용하여 이것을 슬라이스하고 있습니다.
00:14:36그리고 여기서 정말 멋진 점은 이 두 가지가 이제 완전히 구성 가능하고 재사용 가능한 컴포넌트로서 이렇게 함께 작동한다는 것입니다.
00:14:42이것이 우리가 여기서 배우는 이러한 패턴들의 진정한 아름다움입니다.
00:14:45이것을 무엇이든 사용할 수 있습니다.
00:14:50여기 이 모달에도 사용합니다.
00:14:52네, 다음번에 서버 컴포넌트에 어떤 종류의 클라이언트 로직을 추가할지 고려할 때 이것을 기억하세요.
00:14:59좋습니다, 도넛 패턴을 알았습니다.
00:15:01구성 가능한 컴포넌트를 만들고 클라이언트 측 JS를 피하기 위해 그것을 활용하는 방법을 알았으니,
00:15:07이제 마지막 문제로 넘어갈 수 있습니다.
00:15:10다시 이것을 닫겠습니다.
00:15:13그것은 정적 렌더링 전략의 부족과 관련이 있겠죠?
00:15:18빌드 출력을 보면, 실제로 모든 페이지가 동적 페이지로 되어 있습니다.
00:15:24따라서 여기서 무엇인가를 로드할 때마다, 이것은 모든 사용자에게 실행될 것입니다.
00:15:29죄송합니다.
00:15:30이것을 여는 모든 사용자는 이 로딩 상태를 보게 될 것입니다.
00:15:33서버 비용을 낭비하고 성능을 저하시킬 것입니다.
00:15:37그리고 그것은 또한 제 페이지 내부에 있는 무언가가 동적 렌더링을 유발하거나 모든 페이지에 동적 렌더링을 강제하고 있다는 의미입니다.
00:15:45사실, 그것은 제 루트 레이아웃 안에 있습니다.
00:15:49여러분도 이런 경험을 해보셨는지 모르겠습니다.
00:15:51여기 있습니다.
00:15:53제 헤더에 이 사용자 프로필이 있습니다.
00:15:57그리고 이것은 물론 쿠키를 사용하여 현재 사용자를 가져오고 있으며,
00:16:00그것은 다른 모든 것도 동적으로 렌더링된다는 의미입니다.
00:16:02다시 말하지만, 페이지는 동적이거나 정적일 수 있으니까요, 그렇죠?
00:16:06이것은 꽤 흔한 문제이며 이전 Next 버전에서도 해결된 적이 있으니, 우리가 무엇을 할 수 있을지 봅시다.
00:16:13한 가지 방법은 라우트 그룹을 만들고 앱을 정적 및 동적 섹션으로 분할하여 '소개' 페이지를 추출하는 것입니다.
00:16:23이것을 정적으로 렌더링할 수 있습니다.
00:16:25일부 앱에서는 괜찮지만,
00:16:26제 경우에는 중요한 페이지가 제품 페이지이고 이것은 여전히 동적이므로 그다지 도움이 되지 않습니다.
00:16:33이 전략은 어떻습니까?
00:16:35여기서 저는 URL에 특정 상태를 인코딩하는 요청 컨텍스트 `param`을 생성하고,
00:16:40`generate static params`를 사용하여 페이지의 모든 다른 변형을 생성할 수 있습니다.
00:16:46그것은 실제로 클라이언트 측에서 사용자 데이터를 가져오는 것과 결합되어 제 제품 페이지에서 이것을 캐시할 수 있게 해줄 것입니다.
00:16:54확실히 실행 가능한 패턴입니다.
00:16:55Vercel Flags SDK에서 권장하는 'precompute pattern'이라고 생각합니다.
00:17:00하지만 이것은 정말 복잡하고, 저는 데이터를 가져오는 여러 가지 방법이 있습니다.
00:17:04그리고 사실, 제 전체 앱을 이렇게 다시 작성하고 싶지 않습니다.
00:17:07그럼 이런 해결책들을 전혀 사용하지 않아도 된다면 어떨까요?
00:17:10더 간단한 방법이 있다면요?
00:17:12네, 있습니다.
00:17:14다시 우리 애플리케이션으로 돌아가 봅시다.
00:17:17그래서 우리는 `next config`로 가서 `cache components`를 활성화할 수 있습니다.
00:17:23오, 좋습니다.
00:17:25좋습니다.
00:17:25그리고 이것이 하는 일은,
00:17:27기조연설에서 아시다시피,
00:17:29우리의 모든 비동기 호출을 요청 시간 또는 동적으로 전환할 것입니다..
00:17:34그리고 서스펜드되지 않은 비동기 호출이 있을 때마다 오류를 발생시키고,
00:17:39페이지,
00:17:40함수 또는 컴포넌트를 세밀하게 캐시할 수 있는 `use cache` 디렉티브를 제공할 것입니다.
00:17:48네, 그럼 이것을 활용해 봅시다.
00:17:51여기 홈 페이지부터 시작할 수 있습니다.
00:17:55한번 봅시다.
00:17:56다시 말하지만, 저는 정적 및 동적 콘텐츠가 혼합되어 있습니다.
00:17:59저를 위한 환영 배너, 여러분을 위한 것도 저를 위한 것입니다.
00:18:03이 UI 헬퍼로 다시 한번 살펴보겠습니다.
00:18:06예를 들어, 배너는 여기 이것으로 동적으로 렌더링됩니다.
00:18:10반면에 저는 이것을 하이브리드 렌더링으로 표시했습니다.
00:18:14히어로가 이 비동기적인 것을 가져오고 있고 꽤 느리게 진행되기 때문입니다..
00:18:18하지만 사용자 데이터나 동적 API에 의존하지 않습니다.
00:18:21따라서 여기서 하이브리드 렌더링된 모든 것은 실제로 요청과 사용자 간에 재사용될 수 있습니다.
00:18:27그리고 우리는 그것에 `use cache` 디렉티브를 사용할 수 있습니다.
00:18:30그럼 여기에 `use cache` 디렉티브를 추가하고 이것을 캐시된 것으로 표시하겠습니다.
00:18:35그러면 제가 이 페이지를 새로고침할 때마다... 제가 저장하지 않았네요.
00:18:43됐습니다.
00:18:44이 부분은 캐시되었기 때문에 다시 로드되지 않을 것입니다.
00:18:47이제 정적입니다, 그렇죠?
00:18:49그리고 캐시 태그와 같은 다른 관련 API도 있습니다.
00:18:54이것을 타입화하거나 특정 캐시 항목을 세밀하게 검증하거나 재검증 기간을 정의할 수 있게 해줍니다..
00:19:01하지만 이 데모에서는 일반 디렉티브에만 집중하겠습니다.
00:19:05이제 `use cache` 디렉티브가 있으므로,
00:19:07이 히어로 주변의 서스펜스 경계를 실제로 제거할 수 있습니다.
00:19:10그리고 그것은...
00:19:12음,
00:19:12이것이 하는 일은 부분 사전 렌더링이 실제로 이것을 정적으로 미리 렌더링된 셸에 포함시킬 수 있다는 것입니다.
00:19:19그래서 이 히어로는 이 경우 제 빌드 출력의 일부가 될 것입니다..
00:19:23이 페이지에서 공유될 수 있는 다른 모든 것에도 동일하게 적용해 봅시다.
00:19:28예를 들어, 여기에 이 기능 카테고리가 있습니다.
00:19:31거기에도 동일하게 적용해 봅시다.
00:19:33그리고 `use cache` 디렉티브를 추가하고 이것을 캐시된 것으로 표시하겠습니다.
00:19:37이렇게요.
00:19:39그리고 서스펜스 경계를 제거할 수 있습니다.
00:19:40더 이상 이것이 필요하지 않을 것입니다.
00:19:43기능 제품도 마찬가지입니다.
00:19:44`use cache`를 추가하고 이것을 캐시된 것으로 표시하겠습니다.
00:19:48앗.
00:19:50그리고 서스펜스 경계를 제거합니다.
00:19:52여기서 제가 얼마나 많은 복잡성을 제거할 수 있었는지 주목하세요.
00:19:55더 이상 스켈레톤이나 이전에 했던 누적 레이아웃 이동에 대해 걱정할 필요가 없습니다.
00:20:00그리고 페이지는 더 이상...
00:20:02또는 우리는 더 이상 이 페이지 수준의 정적 대 동적 제한을 가지고 있지 않습니다..
00:20:07그래서 이제 이 페이지를 로드하면,
00:20:10진정으로 사용자에게 특화된 콘텐츠를 제외하고 여기 모든 것이 캐시되어 있는 것을 볼 수 있습니다.
00:20:16맞습니다.
00:20:18정말 멋지죠.
00:20:19둘러보기 페이지로 가서 거기서도 동일하게 해 봅시다.
00:20:24네.
00:20:25여기 모든 경계를 이미 표시해 두었으니 무슨 일이 일어나는지 쉽게 이해하실 수 있을 겁니다.
00:20:29그리고 저는 적어도 이 카테고리들을 캐시하고 싶습니다.
00:20:33하지만 오류가 발생하는 것 같습니다.
00:20:37이것을 알아보실 수도 있습니다.
00:20:38즉, 블로킹 라우트가 있다는 의미입니다.
00:20:40그리고 서스펜스 경계를 사용해야 할 때 사용하지 않고 있습니다.
00:20:43새로고침하니, 정말 그렇네요, 그렇죠?
00:20:46이것은 정말 느립니다.
00:20:47그리고 성능 문제와 좋지 않은 UX를 유발하고 있습니다.
00:20:50그래서 이것은 좋습니다.
00:20:51`use cache` 또는 캐시 컴포넌트가 제 블로킹 라우트를 식별하는 데 도움을 주고 있습니다.
00:20:55실제로 그 안에서 무슨 일이 일어나는지 봅시다.
00:20:57이것이 문제죠, 그렇죠?
00:20:59저는 이 카테고리들을 최상위 수준에서 가져오고 있고, 그 위에 어떤 서스펜스 경계도 없습니다.
00:21:03기본적으로 우리는 선택을 해야 합니다.
00:21:05위에 서스펜스 경계를 추가하거나 캐싱을 선택하거나 둘 중 하나입니다.
00:21:09먼저 간단한 것을 해봅시다. 여기에 `loading.tsx`를 추가하겠습니다.
00:21:12그리고 여기에 로딩 페이지를 추가하고, 멋진 스켈레톤 UI를 넣어봅시다.
00:21:21꽤 좋습니다.
00:21:21오류는 해결되었지만, 기다리는 동안 이 페이지에서 유용한 일이 아무것도 일어나지 않습니다.
00:21:25검색조차 할 수 없습니다.
00:21:27따라서 캐시 컴포넌트를 사용하면 동적은 마치... 또는 정적 대 동적은 저울과 같습니다.
00:21:33그리고 페이지에 얼마나 많은 정적 요소를 넣을지는 우리에게 달려 있습니다.
00:21:37그럼 이 페이지를 더 정적으로 전환하고 이 `loading.tsx`를 다시 삭제하겠습니다.
00:21:43그리고 이전에 배웠던 패턴들을 활용하여 이 데이터 페치를 컴포넌트 안으로 밀어 넣고 UI와 함께 배치하겠습니다.
00:21:50그래서 이것을 여기 제 반응형 카테고리 필터로 옮기겠습니다.
00:21:54반응형 디자인 때문에 두 개가 있습니다.
00:21:57실제로 여기에 추가할 수 있습니다.
00:22:01앗.
00:22:03그리고 이것을 임포트합니다.
00:22:05더 이상 이 prop가 필요하지 않습니다.
00:22:06실제로 제 컴포넌트가 더 구성 가능해지고 있습니다.
00:22:09그리고 서스펜드하는 대신, `use cache` 디렉티브를 추가하겠습니다.
00:22:14그것으로 충분할 것입니다.
00:22:16제가 프라미스를 어디서 해결하고 있는지 더 많이 생각하게 되고,
00:22:20이를 통해 컴포넌트 아키텍처를 실제로 개선하고 있다는 점에 주목하세요.
00:22:24이것을 서스펜드할 필요가 없습니다.
00:22:25이것은 여기 정적 셸에 포함될 것입니다.
00:22:28제품 목록은 그냥 신선하게 유지하겠습니다.
00:22:35그래서 매번 새로고침할 수 있습니다.
00:22:37반면에 하단의 카테고리들은 이것도 캐시하고 싶습니다.
00:22:41그럼 푸터로 가봅시다.
00:22:44그리고 여기서 도넛 패턴을 사용하고 있으므로,
00:22:48이것은 상호작용적인 UI 부분 안에 있더라도 실제로 캐시될 수 있습니다.
00:22:54그래서 이것은 전혀 문제가 없습니다.
00:22:55따라서 그 패턴은 구성뿐만 아니라 캐싱에도 좋았습니다.
00:22:58거기에 오류가 하나 더 있는 것 같습니다.
00:23:03그것이 무엇인지 봅시다.
00:23:04여전히 이 오류가 있습니다.
00:23:08이것은 사실 이 검색 `params` 때문입니다.
00:23:10검색 `params`는 아시다시피 동적 API입니다.
00:23:12이것을 캐시할 수 없습니다.
00:23:13하지만 UI의 더 많은 부분을 드러내고 정적으로 만들기 위해 더 깊이 해결할 수 있습니다.
00:23:18그럼 이것을 아래로 옮겨서 제품 목록에 프라미스로 전달하겠습니다.
00:23:24여기서 이것을 프라미스로 타입화하겠습니다, 이렇게요.
00:23:30제품 목록 내부에서 해결하고, 여기와 여기에서 해결된 검색 매개변수를 사용합시다.
00:23:36그리고 이것이 여기서 서스펜드되었으므로, 오류는 사라질 것입니다.
00:23:40그래서 이것을 새로고침하면, 여기서 새로고침되는 유일한 부분은 제가 특별히 동적으로 선택한 부분뿐입니다.
00:23:48다른 모든 것은 캐시될 수 있습니다.
00:23:49그리고 그것은 제가 배너와 상호작용하거나 심지어 검색할 수도 있다는 의미입니다.
00:23:54그 부분이 이미 미리 렌더링되었기 때문입니다..
00:23:57좋습니다, 이제 마지막 페이지인 제품 페이지를 해봅시다. 가장 어렵고 가장 중요한 페이지입니다.
00:24:05지금은 정말 좋지 않습니다.
00:24:08전자상거래 플랫폼에서는 이것이 매우 중요합니다, 분명히요.
00:24:11좋습니다, 그것도 고쳐봅시다.
00:24:15여기 제품 페이지가 있습니다.
00:24:18여기서 재사용 가능한 콘텐츠, 예를 들어 제품 자체만 캐싱하기 시작합시다.
00:24:23그리고 여기에 `use cache`를 추가하고 이것을 캐시된 것으로 표시하겠습니다.
00:24:27그것으로 괜찮을 것입니다.
00:24:28그것은 우리가 여기 서스펜스 경계를 제거할 수 있다는 의미입니다.
00:24:33좋습니다, 그리고 이것은 더 이상 여기 모든 요청에서 새로고침되지 않습니다, 그렇죠?
00:24:38제품 상세 정보에 대해서도 동일하게 해 봅시다.
00:24:40`use cache`를 추가합시다.
00:24:41캐시된 것으로 표시하고 그것도 작동하는지 봅시다.
00:24:47작동하지 않았습니다.
00:24:48실제로 이것은 다른 오류입니다.
00:24:50이 캐시된 세그먼트 안에서 동적 API를 사용하려고 한다고 알려주고 있습니다.
00:24:54그리고 그것은 사실입니다.
00:24:54저는 '제품 저장' 버튼을 사용하고 있습니다, 그렇죠?
00:24:56그것은 제가 클릭하여 저장 상태를 토글할 수 있게 해주었습니다.
00:25:00그럼 이것으로 무엇을 할 수 있다고 생각하십니까?
00:25:03도넛 패턴을 다시 사용할 수 있습니다.
00:25:06실제로 동적 세그먼트를 캐시 세그먼트에 슬롯으로 넣을 수도 있습니다.
00:25:10그래서 우리는 이전처럼 그것들을 인터리빙하고 있지만, 캐시와 함께입니다.
00:25:12정말 멋지죠.
00:25:14그럼 여기에 `children`을 이렇게 추가해 봅시다.
00:25:19그리고 이것이 오류를 제거할 것입니다.
00:25:21그리고 저는 이것을 페이지의 이 하나의 동적 세그먼트 주변에 감싸고,
00:25:26서스펜스 경계를 제거하고,
00:25:28페이지의 그 하나의 동적 부분에 아주 작은 북마크 UI를 추가할 수 있습니다.
00:25:34그리고 이제 어떻게 보이는지 봅시다.
00:25:40거의 전체 UI를 사용할 수 있지만, 동적인 이 작은 조각이 하나 있다는 점에 주목하세요.
00:25:45그리고 그것은 괜찮습니다..
00:25:47다른 모든 것은 여전히 있습니다.
00:25:48그리고 리뷰는 동적으로 남겨둡시다. 신선하게 유지할 수 있으니까요.
00:25:53오류가 하나 더 있습니다.
00:25:54그것을 빠르게 해결해 봅시다.
00:25:56다시, 이것은 `params`입니다.
00:25:58로딩 대체 콘텐츠를 추가하거나 이것을 캐시하거나 둘 중 하나를 선택해야 한다는 도움을 받고 있습니다.
00:26:04이 경우에는 `generate static params`를 사용합시다.
00:26:07사용 사례와 데이터 세트에 따라 다릅니다.
00:26:10하지만 이 경우에는 미리 렌더링된 몇 개의 미리 정의된 페이지를 추가하고,
00:26:14나머지는 사용자가 생성하는 대로 캐시할 것입니다.
00:26:17그리고 이것이 여기 제 오류를 제거할 것입니다.
00:26:20그래서 저는 실제로 리팩토링을 마쳤다고 생각합니다.
00:26:22배포된 버전을 살펴보고 어떻게 보이는지 봅시다.
00:26:26방금 이것을 Vercel에 배포했습니다.
00:26:27그리고 기억하세요, 저는 여기서 많은 데이터 페치를 의도적으로 느리게 만들었습니다.
00:26:35그런데도, 이 페이지를 처음 로드할 때 모든 것이 이미 사용 가능합니다.
00:26:40여기서 유일한 것은 할인 및 '추천'과 같은 몇몇 동적 세그먼트뿐입니다.
00:26:46모두 둘러보기 페이지도 마찬가지입니다.
00:26:47모든 UI가 이미 사용 가능합니다.
00:26:50그리고 제품 자체는 즉각적으로 느껴집니다.
00:26:54그리고 다시 기억하세요, 이 모든 캐시 세그먼트는 부분 사전 렌더링을 통해 정적 셸에 포함될 것입니다.
00:27:00그리고 새로운 Next 16 클라이언트 라우터의 개선된 프리페칭을 사용하여 미리 가져올 수 있습니다.
00:27:05그래서 모든 탐색이 그냥... 정말 빠르다고 느껴집니다, 그렇죠?
00:27:09좋습니다, 요약하자면, 캐시 컴포넌트를 사용하면 더 이상 정적 대 동적의 구분이 없습니다.
00:27:17그리고 동적 API를 피하거나 동적 콘텐츠를 타협할 필요가 없습니다.
00:27:28그리고 제가 보여드렸듯이,
00:27:30단 한 번의 캐시 히트를 위해 여러 데이터 페칭 전략을 사용하는 이러한 복잡한 해킹과 해결책을 건너뛸 수 있습니다.
00:27:37따라서 최신 Next.js에서는 동적 대 정적은 저울과 같습니다.
00:27:40그리고 우리는 앱에 얼마나 많은 정적 요소를 넣을지 결정합니다.
00:27:43그리고 특정 패턴을 따르는 한,
00:27:45기본적으로 성능이 좋고,
00:27:46구성 가능하며,
00:27:47확장 가능한 하나의 멘탈 모델을 가질 수 있습니다.
00:27:50그럼 슬라이드로 돌아가 봅시다.
00:27:53만약 그 속도에 이미 감명받지 않으셨다면, 이것은 Lighthouse 점수입니다.
00:27:56Vercel Speed Insights를 사용하여 일부 필드 데이터를 수집했습니다.
00:28:00따라서 가장 중요한 페이지인 홈 페이지, 제품 페이지, 제품 목록 페이지 모두에서 100점을 받았습니다.
00:28:05비록 이 페이지들이 매우 동적임에도 불구하고 말이죠..
00:28:08그럼 마지막으로 Next.js 앱에서 확장성과 성능을 보장하고 최신 혁신을 활용하여 이와 같은 점수를 얻을 수 있게 해주는 패턴들을 요약해 봅시다.
00:28:18첫째,
00:28:19컴포넌트 트리 깊숙이 프라미스를 해결하고 React Cache를 사용하여 컴포넌트 내부에서 데이터를 로컬로 가져와 중복 작업을 피함으로써 아키텍처를 개선할 수 있습니다.
00:28:28React Use와 결합된 컨텍스트 프로바이더를 사용하여 클라이언트 컴포넌트에 과도한 prop 전달을 피할 수 있습니다.
00:28:35둘째,
00:28:35도넛 패턴을 사용하여 서버 및 클라이언트 컴포넌트를 구성함으로써 클라이언트 측 JavaScript를 줄이고,
00:28:40관심사의 명확한 분리를 유지하며,
00:28:41컴포넌트 재사용을 가능하게 할 수 있습니다.
00:28:43그리고 이 패턴은 나중에 구성된 서버 컴포넌트를 캐시할 수 있도록 더욱 도와줄 것입니다.
00:28:50마지막으로,
00:28:50`use cache`를 사용하여 페이지,
00:28:52컴포넌트 또는 함수 단위로 캐시하고 미리 렌더링하여 중복 처리를 제거하고 성능 및 SEO를 향상시키며,
00:28:57부분 사전 렌더링이 앱의 이러한 세그먼트를 정적으로 렌더링하도록 할 수 있습니다.
00:29:01그리고 콘텐츠가 진정으로 동적이라면, 적절한 로딩 대체 콘텐츠와 함께 서스펜드할 수 있습니다.
00:29:07그리고 이 모든 것이 연결되어 있다는 것을 기억하세요.
00:29:09따라서 아키텍처가 좋을수록 구성하기 쉽고, 최상의 결과로 캐시하고 미리 렌더링하기가 더 쉬울 것입니다.
00:29:15예를 들어,
00:29:15트리 깊숙이 동적 API를 해결하면 더 큰 부분적으로 미리 렌더링된 정적 셸을 만들 수 있습니다.
00:29:22이것으로, 여기는 완성된 애플리케이션 버전의 레포지토리입니다.
00:29:25제가 보여드리지 않은 많은 것들이 있으니 확인해 보실 수 있습니다.
00:29:29그리고 직접 사진을 찍고 입력하고 싶지 않으시다면,
00:29:32QR 코드를 스캔하여 레포지토리와 함께 제 소셜 미디어를 찾으실 수 있습니다.
00:29:36네, 제가 준비한 것은 여기까지입니다.
00:29:37Next.js Conf에 초대해 주셔서 감사합니다.
00:29:39[음악 재생]

Key Takeaway

최신 Next.js에서는 `use cache` 디렉티브와 스마트한 아키텍처 패턴을 통해 정적 및 동적 콘텐츠를 유연하게 조합하여 높은 성능과 확장성을 갖춘 애플리케이션을 구축할 수 있습니다.

Highlights

Next.js 16의 `use cache` 디렉티브는 정적 및 동적 렌더링을 유연하게 혼합하여 애플리케이션 성능을 최적화합니다.

React Cache와 `use` 훅을 활용하여 컴포넌트 트리 깊숙이 데이터를 가져오고 prop 드릴링을 줄여 아키텍처를 개선합니다.

도넛 패턴을 사용하여 서버 컴포넌트 주변에 클라이언트 컴포넌트 래퍼를 만들어 클라이언트 측 JavaScript를 최소화하고 컴포넌트 재사용성을 높입니다.

`cache components` 설정을 활성화하고 `use cache` 디렉티브를 사용하여 페이지, 컴포넌트 또는 함수 단위로 세밀하게 캐싱하고 미리 렌더링할 수 있습니다.

이러한 구성, 캐싱, 아키텍처 패턴들을 적용하여 Lighthouse 점수 100점을 달성하는 등 Next.js 앱의 확장성과 성능을 크게 향상시킬 수 있습니다.

Timeline

소개 및 강연 개요

발표자 오로라는 노르웨이 출신 웹 개발자이자 Crane Consulting의 컨설턴트로, Next.js 앱 라우터를 적극적으로 사용하고 있음을 밝힙니다. 그녀는 최신 Next.js에서 확장성과 성능을 보장하는 구성, 캐싱, 아키텍처 관련 패턴들을 소개할 예정입니다. 이 강연은 Next.js 앱의 일반적인 문제들을 해결하고 더 빠르고 유지보수하기 쉽게 만드는 데 중점을 둡니다. 발표자는 Next.js 16의 새로운 기능과 함께 실제 코드 개선 사례를 보여줄 것을 예고합니다.

정적 및 동적 렌더링 기본 개념

Next.js 앱 라우터의 핵심 개념인 정적 및 동적 렌더링에 대해 설명합니다. 정적 렌더링은 미리 렌더링된 콘텐츠를 캐시하고 전 세계적으로 배포하여 빠른 웹사이트 로드, 서버 부하 감소, SEO 개선에 기여하며 Next.js Conf 웹사이트를 예로 듭니다. 반면 동적 렌더링은 애플리케이션이 실시간 데이터나 개인화된 콘텐츠(예: Vercel 대시보드)를 표시할 수 있도록 하며, 요청 시에만 알 수 있는 사용자 정보와 같은 데이터에 접근할 수 있게 합니다. 이 두 가지 렌더링 방식은 각각의 장단점을 가지고 있습니다.

동적 렌더링을 유발하는 API

페이지를 동적으로 렌더링하게 만드는 특정 API들을 소개합니다. `params` 및 `search params` props 사용, 들어오는 요청의 쿠키와 헤더 읽기, `fetch`를 `data cache configuration no store`와 함께 사용하는 경우가 대표적인 예시입니다. `params`는 `static params`를 통해 미리 렌더링될 페이지 세트를 미리 정의하고 캐시할 수 있지만, 헤더나 쿠키는 빌드 시 알 수 없으므로 캐시하거나 미리 렌더링하려고 하면 오류가 발생합니다. 이러한 API들은 페이지의 렌더링 방식을 결정하는 중요한 요소입니다.

이전 Next.js 버전의 한계와 문제점

이전 Next.js 버전에서는 페이지가 완전히 정적이거나 완전히 동적으로 렌더링되어, 단 하나의 동적 API만 있어도 전체 페이지가 동적으로 전환되는 한계가 있었습니다. React 서버 컴포넌트와 서스펜스를 활용하여 동적 콘텐츠를 스트리밍할 수 있었지만, 여러 비동기 컴포넌트가 동적 페이지에 추가되면 불필요한 스트리밍과 스켈레톤 작업이 필요했습니다. 이는 사용자 정보에 의존하면서도 대부분 정적 데이터를 포함하는 전자상거래 앱과 같이 정적 및 동적 콘텐츠가 혼합된 페이지에서 많은 중복 처리와 성능 저하를 야기했습니다. 이러한 '전부 아니면 전무' 방식은 성능 최적화에 불리했습니다.

Next.js 16의 `use cache` 디렉티브 도입

이전 버전의 한계를 해결하기 위해 작년 Next.js Conf에서 발표되고 올해 Next.js 16에서 사용 가능해진 `use cache` 디렉티브를 소개합니다. `use cache`를 사용하면 페이지가 더 이상 정적 또는 동적 중 하나로 강제되지 않고, 기본적으로 동적이지만 명시적으로 캐싱을 선택할 수 있습니다. 이를 통해 페이지, React 컴포넌트 또는 함수를 캐시 가능으로 표시할 수 있으며, 부분 사전 렌더링을 통해 캐시된 세그먼트가 정적 셸의 일부로 미리 렌더링될 수 있습니다. 이 기능은 개발자가 렌더링 전략을 세밀하게 제어할 수 있도록 돕습니다.

데모 애플리케이션 소개 및 해결할 문제점

발표자는 전자상거래 플랫폼에서 영감을 받은 간단한 Next.js 데모 애플리케이션을 소개합니다. 이 앱은 기능 제품, 카테고리, 제품 목록, 소개 페이지, 사용자 대시보드 등 정적 및 동적 콘텐츠가 혼합되어 있습니다. 현재 코드베이스의 문제점으로는 깊은 prop 드릴링으로 인한 기능 유지보수 및 리팩토링의 어려움, 불필요한 클라이언트 측 JavaScript 및 여러 책임을 가진 대규모 컴포넌트, 그리고 정적 렌더링 부족으로 인한 추가 서버 비용 및 성능 저하가 있습니다. 데모의 목표는 구성, 캐싱, 아키텍처에 관한 스마트한 패턴을 사용하여 이러한 일반적인 문제들을 해결하고 앱을 더 빠르고 확장 가능하며 유지보수하기 쉽게 만드는 것입니다.

Prop 드릴링 해결: React Cache 및 `use` 훅

첫 번째 문제인 prop 드릴링을 해결하기 위해 React Cache와 `use` 훅을 활용하는 방법을 시연합니다. `loggedIn` 변수가 여러 컴포넌트에 전달되는 문제를 해결하기 위해, 데이터 페칭을 사용하는 컴포넌트로 내리고 `auth provider`로 전체 앱을 감싸는 패턴을 사용합니다. 이를 통해 `loggedIn` 값을 개인화 배너 내부에서 로컬로 가져올 수 있게 되어 컴포넌트가 더 구성 가능하고 재사용 가능해지며, 홈페이지의 의존성을 제거합니다. 이 패턴은 컴포넌트 아키텍처를 개선하고 데이터 페칭을 사용하는 컴포넌트와 가깝게 배치하는 데 효과적입니다.

클라이언트 측 JS 감소: 도넛 패턴

과도한 클라이언트 측 JavaScript와 여러 책임을 가진 대규모 컴포넌트 문제를 해결하기 위해 '도넛 패턴'을 소개합니다. 환영 배너의 간단한 '닫기' 상태 때문에 전체 컴포넌트가 클라이언트 컴포넌트로 변환되고 클라이언트 측에서 데이터를 가져오는 문제를 지적합니다. 도넛 패턴은 상호작용 로직을 클라이언트 측 래퍼(`banner container`)로 추출하고, 서버 렌더링 콘텐츠를 `children` 슬롯으로 넣어 클라이언트 측 의존성을 제거하고 서버 측 페칭으로 대체하는 방식입니다. 이를 통해 클라이언트 측 JS를 줄이고 관심사의 분리를 명확히 하여 컴포넌트의 재사용성을 높입니다.

정적 렌더링 부족 문제와 기존 해결책의 한계

정적 렌더링 전략 부족으로 인해 모든 페이지가 동적으로 렌더링되어 서버 비용 낭비와 성능 저하가 발생하는 문제를 다룹니다. 특히 루트 레이아웃의 헤더에 있는 사용자 프로필이 쿠키를 사용하여 현재 사용자를 가져오기 때문에 전체 앱이 동적으로 렌더링되는 흔한 문제를 지적합니다. 라우트 그룹을 사용하여 앱을 정적 및 동적 섹션으로 분할하거나, `generate static params`와 클라이언트 측 데이터 페칭을 결합하는 'precompute pattern'과 같은 기존 해결책들을 언급하지만, 복잡성과 앱 재작성의 필요성 때문에 한계가 있음을 설명합니다. 이러한 해결책들은 종종 개발 복잡성을 증가시켰습니다.

`use cache`를 활용한 홈 페이지 개선

더 간단한 해결책으로 `next config`에서 `cache components`를 활성화하는 방법을 제시합니다. 이 설정은 모든 비동기 호출을 요청 시간 또는 동적으로 전환하고, 서스펜드되지 않은 호출에 오류를 발생시키며, `use cache` 디렉티브를 제공하여 페이지, 함수 또는 컴포넌트를 세밀하게 캐시할 수 있게 합니다. 홈 페이지에 `use cache` 디렉티브를 추가하여 히어로, 기능 카테고리, 기능 제품과 같은 재사용 가능한 콘텐츠를 캐시하고, 서스펜스 경계를 제거하여 부분 사전 렌더링을 통해 정적 셸에 포함되도록 합니다. 이로써 페이지 로드 시 정적 콘텐츠가 즉시 제공되어 성능이 향상됩니다.

`use cache`를 활용한 둘러보기 페이지 개선 및 문제 해결

둘러보기 페이지에도 `use cache`를 적용하여 카테고리를 캐시하려 하지만, 블로킹 라우트 오류가 발생합니다. `loading.tsx`를 추가하여 오류를 해결할 수 있지만, 이는 페이지의 유용성을 떨어뜨리고 검색과 같은 상호작용을 방해합니다. 대신 데이터 페칭을 컴포넌트 내부로 밀어 넣고 `use cache` 디렉티브를 추가하여 페이지를 더 정적으로 전환합니다. `search params`로 인한 동적 API 문제를 해결하기 위해 `params`를 제품 목록 컴포넌트에 프라미스로 전달하여 더 깊이 해결하고 서스펜드함으로써, 페이지의 동적 부분만 새로고침되고 다른 모든 부분은 캐시될 수 있도록 합니다. 이로써 사용자 경험과 성능을 동시에 개선합니다.

`use cache`를 활용한 제품 페이지 개선 및 `generate static params`

가장 중요하고 어려운 제품 페이지를 개선합니다. 제품 자체와 제품 상세 정보에 `use cache`를 추가하여 캐시 가능하게 만듭니다. '제품 저장' 버튼과 같은 동적 API 사용으로 인해 캐시된 세그먼트 내에서 오류가 발생하자, 다시 도넛 패턴을 사용하여 동적 세그먼트를 캐시 세그먼트에 슬롯으로 넣어 인터리빙합니다. `params` 문제에 대해서는 `generate static params`를 사용하여 미리 렌더링될 몇 개의 페이지를 정의하고 나머지는 사용자가 생성하는 대로 캐시하도록 하여 오류를 제거합니다. 이 접근 방식은 제품 페이지의 대부분을 정적으로 유지하면서도 개인화된 동적 상호작용을 가능하게 합니다.

결론 및 핵심 패턴 요약

리팩토링된 앱의 배포 버전을 보여주며, 의도적으로 느리게 설정된 데이터 페칭에도 불구하고 페이지 로드 시 모든 것이 즉시 사용 가능함을 강조합니다. Next.js 16의 클라이언트 라우터 개선된 프리페칭과 부분 사전 렌더링 덕분에 모든 탐색이 매우 빠르다고 설명합니다. `cache components`를 사용하면 정적/동적 구분이 사라지고, 복잡한 해결책 없이도 성능과 확장성을 확보할 수 있음을 강조합니다. 마지막으로, 아키텍처 개선(React Cache, `use` 훅), 클라이언트 측 JS 감소(도넛 패턴), 그리고 캐싱 및 미리 렌더링(`use cache`)이라는 세 가지 핵심 패턴을 요약하며, 이들이 상호 연결되어 최상의 결과를 가져온다고 설명합니다. 발표자는 Lighthouse 점수 100점을 달성한 사례를 보여주며 강연을 마무리합니다.

Community Posts

View all posts