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[음악 재생]