Home

읽기 설정

네, 안녕하세요 여러분. 제 이름은 오로라입니다. 저는 노르웨이 출신의 웹 개발자예요. 크레인 컨설팅에서 컨설턴트로도 일하고 있고, 현재 컨설팅 프로젝트에서는 Next.js 앱 라우터를 적극적으로 사용하고 있습니다.00:05

오늘 강의에서는 현대적인 Next.js에서 컴포지션, 캐싱, 그리고 아키텍처와 관련된 패턴들을 알려드릴 건데요, 이걸 통해 확장성과 성능을 향상시키는 데 도움이 될 거예요. 먼저 이 강연에서 가장 기본적인 개념인 정적 렌더링과 동적 렌더링을 복습해 보도록 하겠습니다.00:16

저희는 넥스트.js 앱 라우터에서 이 둘을 모두 만날 수 있습니다. 정적 렌더링은 미리 렌더링된 콘텐츠를 캐시하고 전 세계적으로 분배할 수 있기 때문에 웹사이트 로딩 속도를 향상시킬 수 있으며, 사용자 분들이 콘텐츠에 더욱 빠르게 접근할 수 있도록 돕습니다.00:30

더 빠르게 할 수 있습니다. 예를 들어 Next.js Conf 웹사이트를 참고해 보세요.00:42

정적 렌더링은 콘텐츠가 각 사용자 요청마다 생성될 필요가 없기 때문에 서버 부하를 줄여줍니다. 미리 렌더링된 콘텐츠는 페이지 로드 시 콘텐츠가 이미 제공되기 때문에 검색 엔진 크롤러가 색인화하기도 더 쉽습니다.00:45

반응형 렌더링은 저희 애플리케이션이 실시간 또는 자주 업데이트되는 데이터를 표시할 수 있도록 해줍니다. 또한, 대시보드나 사용자 프로필과 같은 개인화된 콘텐츠를 제공하는 데에도 활용될 수 있습니다. 예를 들어, Vercel 대시보드를 말씀드릴 수 있습니다.01:00

동적 렌더링을 통해 요청 시점에야 알 수 있는 정보를 활용할 수 있습니다.01:12

이 경우, 누가 대시보드를 보고 있는지인데, 바로 저입니다. 특정 API 호출 때문에 페이지가 동적으로 렌더링될 수도 있어요. 페이지나 훅에 전달되는 params나 search params props를 사용하면 동적 렌더링이 발생합니다.01:16

하지만, params를 사용하면 generate static params를 통해 미리 렌더링된 페이지 세트를 미리 정의할 수 있고, 사용자들에 의해 페이지가 생성되는 동안 캐시할 수도 있습니다. 게다가, 들어오는 요청의 쿠키와 헤더를 읽으면 페이지가 동적 렌더링으로 전환됩니다.01:32

params와 달리, 헤더나 쿠키를 이용해서 캐싱하거나 미리 렌더링하려고 하면 빌드 과정에서 오류가 발생합니다. 빌드 시점에 알 수 없는 정보이기 때문입니다. 마지막으로, 데이터 캐시 구성에서 no store 옵션을 사용한 fetched도 동적 렌더링을 강제합니다.01:46

몇 가지 더 동적 렌더링을 유발할 수 있는 API들이 있습니다만, 주로 접하는 것들입니다.02:01

Next.js 이전 버전에서는 페이지가 완전히 정적 또는 완전히 동적으로 렌더링되었습니다.02:08

페이지 하나에 단 하나의 동적 API만 있어도 페이지 전체가 동적 렌더링으로 바뀌게 됩니다. 예를 들어, 쿠키 값을 확인하는 간단한 인증을 하는 경우도 그렇습니다.02:13

React Server Components와 Suspense를 활용하면, 개인화된 환영 배너나 추천 상품과 같이 동적인 콘텐츠를 준비되는 대로 스트리밍 방식으로 표시하고, Suspense를 통해 폴백을 제공하면서 동시에 뉴스레터와 같은 정적인 콘텐츠를 보여줄 수 있습니다.02:22

하지만 동적인 페이지에 여러 비동기 컴포넌트를 추가하게 되면, 예를 들어 특별한 상품 기능과 같이, 동적인 API에 의존하지 않더라도 요청 시점에 함께 실행될 수 있습니다.02:35

초기 페이지 로딩을 막기 위해, 우리는 그 컴포넌트들을 일시 중단하고 스트리밍 방식으로 불러오면서 추가적인 작업을 하고, 스켈레톤을 만들고, 누적 레이아웃 시프트 같은 문제까지 신경 써야 합니다. 하지만 페이지는 종종 정적 콘텐츠와 동적 콘텐츠가 혼합되어 있는 경우가 많습니다.02:47

예를 들어, 사용자 정보를 활용하면서도 대부분 정적인 데이터를 포함하는 이커머스 앱과 같은 경우를 생각해 볼 수 있습니다.03:01

정적 또는 동적 방식 중 하나를 선택해야 하는 상황은 서버에 불필요한 처리 부담을 주며, 거의 변경되지 않거나 아주 드물게 변경되는 콘텐츠의 성능 최적화에도 적합하지 않습니다.03:07

그래서 이 문제를 해결하기 위해, 작년 NextJS 컨퍼런스에서 Use Cache Directive가 발표되었고, 올해 키노트에서 보셨듯이 NextJS 16에서 사용할 수 있게 되었습니다.03:21

그래서 유즈 캐시를 사용하면 페이지가 정적 렌더링 또는 동적 렌더링 중 어느 한 가지로 강제되지 않습니다. 둘 다 가능하며, Next.js는 더 이상 params와 같은 항목에 접근하는지 여부에 따라 페이지가 무엇인지 추측할 필요가 없습니다.03:31

기본적으로 모든 것이 동적이며, Use Cache를 사용하면 명시적으로 캐싱을 선택할 수 있습니다. Use Cache는 컴포저블 캐싱을 가능하게 합니다. 페이지, React 컴포넌트, 또는 함수 중 하나를 캐시 가능하다고 지정할 수 있습니다.03:43

여기서는 피처 제품 컴포넌트를 실제로 캐싱할 수 있습니다. 왜냐하면 요청과 처리 과정이 필요 없고, 동적 API를 사용하지 않기 때문입니다.03:57

이 캐시 세그먼트들은 추가적으로 사전 렌더링되어, 부분적인 사전 렌더링을 통해 정적 쉘의 일부로 포함될 수 있습니다. 즉, 기능 제품들은 페이지 로드 시 즉시 사용 가능하며 스트리밍 과정이 필요하지 않습니다.04:05

자, 이제 중요한 배경 지식을 갖게 되었으니 데모를 진행해 보겠습니다. Next.js 앱에서 흔히 겪는 문제들을 개선하는 코드 베이스를 보여드리려고 합니다.04:16

이러한 문제점들에는 깊이 중첩된 프로퍼티 접근 방식, 리팩토링이 어렵게 만드는 기능, 불필요한 클라이언트 측 JavaScript 코드, 여러 책임을 가진 거대한 컴포넌트, 그리고 정적 렌더링 부재로 인한 추가적인 서버 비용 및 성능 저하 등이 포함됩니다. 네, 맞습니다.04:24

시작해 봅시다.04:36

이쪽으로 부탁드립니다.04:44

네, 좋습니다. 이것은 아주 간단한 애플리케이션인데요, 전자상거래 플랫폼에서 영감을 받았습니다. 간단하게 시연을 보여드리겠습니다. 이 파일을 로드해볼게요.04:48

페이지에 콘텐츠가 몇 가지 있습니다. 예를 들어 기능 제품이나 기능 카테고리, 다양한 제품 데이터 등이 있습니다. 또한, 여기에는 모든 제품을 볼 수 있는 ‘전체 둘러보기’ 페이지도 있습니다.05:03

플랫폼 내 제품들과 페이지 사이의 연관성을 살펴볼 수 있습니다. 그리고 여기에는 정적인 '소개' 페이지도 있습니다. 사용자로서 로그인하여 확인할 수도 있습니다.05:16

저를 사용자 계정에 로그인시켜 주고, 또한 대시보드에서 개인 맞춤 콘텐츠를 제공받을 수 있도록 해줍니다. 예를 들어, 추천 상품이나 개인 맞춤 할인 정보 같은 것들이 여기에 표시됩니다. 다양한 콘텐츠가 적절히 섞여 있는 것을 주목해 주시고, 추가 페이지도 있습니다.05:28

제가 보여드리지 못했던 제품 페이지인데, 가장 중요한 부분입니다. 여기서 제품 정보를 확인하고, 필요하다면 저희 사용자분을 위해 저장할 수도 있습니다. 이 앱에 정적 콘텐츠와 동적 콘텐츠가 적절하게 섞여 있다는 점을 참고해 주세요.05:44

저희 사용자에 의존하는 기능들이 많기 때문에, 이쪽에 위치한 코드를 함께 살펴보겠습니다.05:57

네, 넥스트제이티(Next.js) 16에서 앱 라우터를 사용하고 있습니다. About 페이지, All 페이지, 그리고 Product 페이지와 같이 다양한 페이지들을 가지고 있습니다.06:05

저도 기능 분할을 활용해서 앱 폴더를 깔끔하게 유지하고 있습니다. 프리스마를 이용해서 데이터베이스와 연동하는 다양한 컴포넌트와 쿼리들이 있습니다.06:13

네, 이 모든 과정을 의도적으로 느리게 설정해서 그런지 로딩 시간이 상당히 길어집니다. 이렇게 하면 무슨 일이 일어나는지 좀 더 쉽게 파악할 수 있습니다.06:23

저희가 이 애플리케이션에서 개선하고 싶었던 주요 문제점들은 깊이 중첩된 prop 전달로 인해 유지보수가 어렵고, 외부 팩터 기능, 클라이언트 측 자바스크립트, 그리고 정적 렌더링 부재로 인한 추가적인 서버 비용 등이 있었습니다.06:31

여기 데모의 목표는 기본적으로 이 앱을 개선하는 것입니다. 특히, 컴포지션 캐싱 및 아키텍처 측면에서 스마트한 패턴을 적용하여 일반적인 기능을 수정하고 더 빠르고 확장 가능하며 유지보수하기 쉽게 만드는 데 집중하고 있습니다.06:45

네, 그럼 그 부분부터 시작해 보겠습니다. 저희가 먼저 해결하고 싶은 문제는 사실 프로프 칠링과 관련된 내용인데, 그 부분은 여기 페이지에 있습니다.07:01

여기서 보시면, 저는 이 변수를 최상단에 저장해두고, 이 변수를 몇몇 컴포넌트에 전달하고 계신 것을 확인하실 수 있습니다.07:10

실제로 이 개인 배너를 통해 여러 단계를 거쳐 통과되었기 때문에, 여기서 재사용하기가 어려워질 것 같습니다. 환영 배너를 만들 때 항상 로그인 의존성이 필요하기 때문입니다.07:17

서버 컴포넌트의 경우, 가장 좋은 방법은 데이터를 사용하는 컴포넌트 내에서 실제로 데이터 fetching을 수행하고, promise를 트리 구조 내에서 더 깊숙이 resolve하는 것입니다.07:28

이 기능이 더욱 신뢰성을 확보하려면, fetch 또는 React 캐시를 사용하는 경우 여러 호출을 중복 제거하고 컴포넌트 내 어디에서든 재사용할 수 있습니다.07:37

네, 그렇게 재사용하셔도 괜찮습니다. 이제 이 부분을 개인화(personalized) 섹션으로 옮길 수 있을 것 같고, 더 이상 이 속성(prop)은 필요하지 않습니다. 그냥 여기다가 바로 넣으면 되고, 실례지만, 이 부분을 더 이상 전달할 필요도 없겠습니다.07:48

이제 이 비동기 호출을 담당자 섹션으로 옮기면서 페이지 로딩이 더 이상 차단되지 않으므로, 간단한 서스펜스로 이를 중단해도 괜찮습니다. 또한, 폴백은 필요하지 않을 것 같습니다. 환영 배너 역시 비슷한 방식으로 처리할 것 같습니다.08:05

여기서 로그인 변수나 값을 가져오려고 하면 안 돼요. 왜냐하면 이건 클라이언트 컴포넌트거든요. 이걸 다른 방식으로 해결해야 하고, 아주 똑똑한 패턴을 이용해서 해결할 겁니다.08:20

이제 레이아웃으로 들어가서 여기 있는 모든 것을 auth provider로 감싸도록 하겠습니다. 앱 전체를 여기로 둘러싸고 이 로그인 변수를 여기에서 가져올게요.08:34

전 분명히 루트 레이아웃 전체를 막고 싶지는 않아요. 여기 있는 await을 제거하고 이 auth provider에 promise로 전달해 봅시다. 이 promise만 담겨 있어도 괜찮습니다. 필요할 때까지 기다릴 수 있으니까요.08:46

자, 이제 이것이 설정되었으니, 이걸 지울 수 있습니다. 우선 이 프로프를 없애고, 개인 배너로 전달하는 이 과정을 없애고, 여기 이 프로프나 시그니처 전달 과정도 없앨 수 있습니다.09:01

이제 이 인증 제공자를 활용하여 개인 배너 내에서 useAuth 훅을 통해 로컬에 로그인된 값을 가져올 수 있습니다. 방금 생성한 이 제공자를 사용해서 읽어낼 수도 있습니다.09:16

이것은 일종의 방식으로, 문제가 해결되는 동안 일시 중단해야 작동할 것 같습니다. 지금은 개인 배너 안에 작은 데이터 가져오기를 배치하여 props를 전달할 필요가 없어졌습니다.09:28

이게 로딩되는 동안, 이 부분도 같이 중단시키고 폴백을 적용해볼게요. 그리고 여기서는 일반적인 배너를 만들어서 어색한 누적 변화를 피하도록 해볼게요.09:40

마지막으로 이것도 제거해 주십시오.09:50

자, 이제 이 환영 배너는 조합 가능하도록 만들었고, 재사용 가능하게 되었으며, 홈페이지에 더 이상 이상한 속성이나 의존성이 없습니다. 이렇게 쉽게 재사용할 수 있으므로, 여기 브라우저 페이지에도 추가해 보겠습니다. 이곳에 위치할 것입니다.09:56

여기서 바로 사용하셔도 괜찮습니다. 별도의 추가 사항은 필요하지 않습니다.10:12

이러한 패턴들을 통해 React 캐시와 react use를 활용하여 좋은 컴포넌트 아키텍처를 유지하고, 컴포넌트의 재사용성과 조합성을 높일 수 있습니다. 좋습니다, 이제 시작해 보겠습니다.10:21

다음으로 흔히 발생하는 문제는 과도한 클라이언트 측 자바스크립트와 여러 책임들을 가진 큰 컴포넌트입니다. 사실 이 또한 전체 페이지에서 나타나고 있으며, 다시 한번 이에 대한 개선 작업이 필요할 것 같습니다.10:32

이 환영 배너는 현재 클라이언트 컴포넌트입니다. 이렇게 클라이언트 컴포넌트인 이유는 여기 아주 간단한 해제 상태가 있어서, 그냥 이걸 클릭하면 됩니다.10:45

UI 상호작용 자체는 괜찮지만, 문제는 제가 이 전체 컴포넌트를 클라이언트 사이드 컴포넌트로 변환했고, 심지어 데이터를 클라이언트 사이드에서 가져오기 위해 use swr을 사용했다는 점입니다.10:55

이제 이 API 레이어가 있는데, 데이터에 대한 타입 안전성이 더 이상 없습니다. 네, 이것은 필요하지 않으며, UI 로직을 포함하고 있어서 관심사 분리 원칙도 깨고 있습니다.11:06

데이터가 있으니, 다른 스마트 패턴을 활용하여 이 문제를 해결해 보겠습니다. 이것을 도넛 패턴이라고 하는데, 기본적으로 클라이언트 측 래퍼로 추출할 계획입니다. 그럼 만들어 보겠습니다.11:16

여기 새로운 컴포넌트를 만들고 배너 컨테이너라고 부르겠습니다. 이 컴포넌트는 사용처 클라이언트 디렉티브를 활용하여 인터랙티브한 로직을 포함할 것입니다. 이제 시그니처를 생성하여 이전에 사용했던 모든 코드를 붙여넣을 수 있습니다.11:28

이 배너들을 사용하는 대신, 여기다가 단순히 자식 컴포넌트를 넣어서 이렇게 만들 겁니다. 그래서 도넛 패턴이라고 부르는데요, 서버 렌더링 콘텐츠를 감싸는 UI 로직을 만드는 것뿐입니다. 아니면 서버 렌더링 콘텐츠를 사용하고, 그 뒤에…11:43

이제 더 이상 클라이언트 측 의존성은 필요하지 않으므로, `useClient`를 제거하고 인증을 비동기적으로 처리하는 `isAuth` 함수를 대신 사용할 수 있습니다. 이 부분을 비동기 서버 함수로 만들 수 있습니다.11:57

우리는 컴포넌트 내에서 클라이언트 측 데이터 요청을 서버 측 데이터 요청으로 대체할 수도 있습니다. 그래서 바로 할인 데이터를 직접 가져와서 활용해 보겠습니다. 기존 방식대로요.12:08

이전과 같은 멘탈 모델을 유지하면서 타입 안전성도 확보할 수 있고, 덕분에 원하지 않는 API 레이어는 삭제해도 괜찮겠습니다.12:19

마지막으로 로딩 부분은 새로운 환영 배너를 내보내서 처리할 수 있습니다. 이 배너는 서버 벤더 콘텐츠를 포함하는 도넛 패턴 배너 컨테이너를 사용할 것입니다. 그렇게 하면 더 이상 이 로딩이 필요 없게 됩니다. 따라서 이 모든 부분을 서버 컴포넌트로 리팩터링하는 것이 기본적으로 됩니다.12:29

오른쪽으로 이동하여 UI 로직을 추출했습니다.12:43

하지만 이게 뭐죠? 또 다른 에러가 났네요. 사실 이건 모션 때문에 발생한 건데요, 모션을 사용하시나요? 정말 멋진 애니메이션 라이브러리이지만, useClient 지시어를 꼭 사용해야 합니다.12:46

다시 한번 말씀드리지만, 애니메이션 때문에만 이 코드를 useClient로 만들 필요는 없어요. 도넛 패턴 래퍼를 다시 만들어서, 이 애니메이션들을 위한 래퍼만 추출할 수 있습니다.13:00

여기 있는 건 클라이언트 사이드로 변환할 필요가 없다는 뜻이에요. 제가 놓치고 있는 부분이 있을 거예요. 아, 네, 여기 있네요.13:11

그래서 이제 여기 있는 모든 게 서버로 변환되었고, 동일한 상호작용을 유지하고 있습니다. 아직 여기서는 인터랙티브 로직을 가지고 있지만, 이제 데이터 가져오는 방법은 하나로 줄었고, 클라이언트 측 JS도 훨씬 적어졌습니다.13:21

사실, 저도 이 UI 경계 헬퍼를 위해 도넛 패턴을 사용하고 있어요. 이렇게요. 그거 보이세요? 이런 식으로 제가 말씀드리는 게 다시 한번 잘 보여주는 것 같죠?13:36

도넛 패턴을 사용하면, 서버 컴포넌트 주위에 이 클라이언트 컴포넌트를 둘 수 있습니다. 그리고 다른 컴포넌트들도 여기 UI 헬퍼로 표시했어요. 여기에도 더 많은 서버 컴포넌트들이 있습니다. 이제 이 부분도 좀 더 개선해 볼까요? 꽤 능숙해졌으니까요.13:46

그 부분은 푸터에 있습니다. 이 카테고리들은, 제가 이 좋은 컴포넌트를 이용해서 데이터를 가져오고 있는데, 혹시 너무 길어질 경우를 대비해서 '더보기' 기능을 추가하고 싶습니다.14:00

도넛 패턴을 활용하면, 쇼어 컴포넌트를 여기에서 간단히 감싸 넣을 수 있습니다.14:14

그리고 이것은 제 UI 로직을 담고 있으며, 이렇게 생겼습니다. 꽤 멋지네요. 그리고 이것은...14:20

이제 클라이언트 로직을 포함하고 있어서, 상태를 사용할 수 있습니다. 자식 요소의 개수를 배열에 넣어 슬라이싱하고 있습니다. 여기서 정말 멋진 점은 이 두 요소가 완전히 조합 가능하고 재사용 가능한 컴포넌트로 함께 작동한다는 것입니다. 이러한 패턴의 아름다움이 바로 이런 점입니다.14:30

여기서 배우시는 거예요. 이걸 뭐든지 활용하실 수 있어요. 저는 이 모달에도 이걸 사용하고 있습니다. 네, 다음번에 서버 컴포넌트에 클라이언트 로직을 추가할 때 이걸 꼭 기억하세요.14:44

알겠습니다, 데이터 패턴도 알았고, 이걸 이용해서 재사용 가능한 컴포넌트를 만들고 랑타(Planta)를 피하는 방법도 알았으니 이제 마지막 문제로 넘어가도 되겠네요. 잠시 이 문제를 닫아도 될까요?14:59

정적 렌더링 전략이 부족해서 그런 거겠죠? 제 빌드 결과물을 보면, 모든 페이지가 동적으로 렌더링되고 있네요.15:13

그래서 제가 여기 뭔가 로드할 때마다, 모든 사용자에게 이 코드가 실행되는 거죠. 죄송합니다. 이 페이지를 여는 모든 사용자는 이 로딩 상태를 경험하게 됩니다.15:23

서버 비용 낭비가 발생하고 성능도 저하될 가능성이 있습니다. 그리고 이는 제 페이지 내부에 어떤 요소가 동적 렌더링을 유발하거나 모든 페이지에 대해 동적 렌더링을 강제하고 있다는 뜻이기도 합니다.15:33

사실, 제 루트 레이아웃 안에 들어가 있어요. 혹시 그러신 경험이 있으셨는지 모르겠네요.15:46

헤더 쪽 여기 있습니다. 사용자 프로필이 있는데, 이건 쿠키를 이용해서 현재 사용자를 가져오고, 다시 말해서, 다른 모든 것도 동적으로 렌더링되고 있습니다. 페이지가 동적일 수도 있고 정적일 수도 있기 때문이죠.15:51

이 문제는 꽤 흔히 발생하는 문제이며, 이전 버전의 Next에서 이미 해결된 적이 있습니다. 지금은 어떻게 처리할 수 있을지 한번 살펴보겠습니다.16:06

하나 가능한 방법은 라우트 그룹을 만들고 앱을 정적 및 동적 섹션으로 나누는 것입니다. 그렇게 하면 제 소개 페이지를 추출하여 정적으로 렌더링할 수 있을 것 같습니다.16:14

어떤 앱에서는 괜찮을 수도 있지만, 제 경우에는 중요한 페이지가 제품 페이지인데, 이 페이지는 아직 동적으로 렌더링되기 때문에 별로 도움이 안 되네요.16:25

이런 전략은 어떨까요? 여기서는 요청 컨텍스트 파라미터를 만들어서 특정 상태를 URL에 인코딩하고, 그런 다음 generate static params를 사용하여 페이지의 다양한 변형을 모두 생성할 수 있습니다.16:33

실제로 클라이언트 측에서 사용자 데이터를 가져오는 것과 결합하면, 상품 페이지에서 캐시 히트를 얻을 수 있을 것 같습니다. 분명히 시도해볼 만한 방법이라고 생각합니다.16:46

음, 버슬 플래그 SDK에서 미리 계산 패턴을 권장한다고 하던데, 하지만 이게 정말 복잡하고 데이터를 가져오는 방법도 여러 가지가 있어서요. 사실 제 앱 전체를 이 방식으로 다시 쓰려고 하지는 않거든요. 만약 우리가 이 모든 우회 작업을 할 필요가 없었다면, 혹시 이렇게 간단하게 해결할 수 있는 방법이 있다면 어떨까요?16:56

더 간단한 방법이 있죠, 음, 다시 저희 애플리케이션으로 돌아가서, 다음 설정에서 캐시 컴포넌트를 활성화할 수 있습니다.17:11

좋네요.17:21

네, 아시다시피 기조 연설에서 말씀드린 것처럼, 저희는 모든 비동기 호출을 요청 시간 또는 동적으로 옵트인할 예정입니다. 또한 비동기 호출이 일시 중단되지 않을 때 오류를 알려줄 것입니다.17:24

그리고 이를 통해 페이지, 함수 또는 컴포넌트를 세분화하여 캐싱할 수 있는 캐시 지시문을 얻게 됩니다.17:38

네, 그럼 이걸 활용해 보도록 하겠습니다. 여기 홈페이지부터 시작할 수 있을 것 같습니다. 한번 살펴볼까요? 다시 한번 말씀드리지만, 여기에는 정적 콘텐츠와 동적 콘텐츠가 혼합되어 있습니다.17:48

제 환영 배너가 있고, 이것은 저를 위해서, 그리고 여러분을 위해서도 저를 위한 것입니다. UI 도우미를 이용해서 다시 한번 살펴보겠습니다. 예를 들어 배너는 여기에서 동적으로 렌더링됩니다, 그렇죠.17:59

이것을 하이브리드 렌더링으로 표시하는 이유는 주인공이 비동기적으로 데이터를 가져오는데 속도가 다소 느리기 때문입니다. 하지만 사용자 데이터나 동적 API에 의존하지 않기 때문에, 전체적으로는 괜찮습니다.18:11

여기 렌더링된 하이브리드 방식은 실제 요청 간, 사용자 간에 재사용될 수 있으며, 사용 캐시 지시문을 적용하여 사용할 수 있습니다. 그래서 여기 사용 캐시 지시문을 추가하고 이것을 캐시로 표시하겠습니다. 그렇게 하면 페이지를 다시 불러올 때마다 문제가 발생하지 않을 것입니다.18:23

음, 제가 그걸 거기에 저장하지 않았네요. 자, 이제 다시 로드되지 않네요. 캐시되어 있어서 그 부분은 고정된 상태가 되었고, uh, 캐시 태그와 같은 관련 API들도 있습니다.18:40

제가 이 항목을 태그하거나 특정 캐시 항목을 세분화하여 검증하거나 검증 기간을 정의하고 싶지만, 이번 데모에서는 우선 간단한 지시사항에 집중해 보겠습니다. 이 사용된 캐시 지시사항을 활용할 수 있게 되었으니, 이제 이 영웅 주변의 서스펜스 경계를 제거할 수 있습니다.18:55

그리고 그것은, 음, 이것이 하는 일은 부분적인 미리 렌더링이 실제로 가능해져서 이 부분을 정적으로 미리 렌더링된 셸에 포함시킬 수 있다는 의미입니다. 따라서 이 히어로가 이 경우에 그 일부가 될 수 있겠죠.19:11

제 빌드 결과입니다. 페이지에 공유 가능한 다른 항목들도 동일하게 처리해 보겠습니다.19:22

예를 들어, 여기에는 이런 기능 범주들이 있습니다. 저쪽에서도 동일하게 처리하고, 캐시 지시문을 추가해서 이렇게 캐시로 표시해 보겠습니다.19:28

이제 더 이상 필요하지 않으니 서스펜스 경계를 제거할 수 있습니다. 기능 제품에도 마찬가지로 적용해서 캐시를 사용하고 캐시로 표시해 보겠습니다. 아, 그리고 서스펜스 경계를 제거하면 복잡도가 얼마나 줄어드는지 확인해 보세요.19:38

여기서 제거할 수 있어서, 이제는 제가 이전에 했었던 스켈레톤이나 누적 레이아웃 쉬프트에 대해 신경 쓸 필요가 없어요. 그리고 페이지는 더 이상, 아니면, 페이지 수준의 정적과 동적 제한이 더 이상 없게 됐어요.19:54

이제 이 페이지를 로드하면, 여기서 모든 것이 캐시되었다는 것을 확인할 수 있을 거예요. 사용자에게 정말 특화된 콘텐츠를 제외하고는요, 그렇죠?20:08

정말 멋지네요. 그럼 브라우즈 페이지로 가서 거기서도 똑같이 해봐요. 네, 제가 여기 모든 경계를 표시해 놨으니까, 무슨 일이 일어나는지 쉽게 이해하실 수 있을 거예요.20:18

이 카테고리들도 최소한 캐시하고 싶네요. 그런데 에러가 나는 것 같기도 합니다.20:29

아마 이 상황을 알고 계실 수도 있습니다. 즉, 제가 막혀 있는 경로를 사용하고 있고, 해야 할 때는 서스펜스 경계를 사용하지 않고 있다는 뜻입니다.20:37

정말 상쾌하네요, 맞죠? 그런데 속도가 정말 느리고, 성능 저하와 좋지 않은 사용자 경험을 초래하고 있어요. 캐시나 캐시 컴포넌트를 활용하는 건 정말 좋은 방법인 것 같습니다. 제 차단 규칙을 파악하는 데 도움이 되고 있어요. 이제 안쪽에서 무슨 일이 일어나는지 한번 살펴보겠습니다. 이, 이것은요...20:44

흠, 제가 가져오려고 하는데 이 카테고리가 최상위 레벨이고 위에 경계가 없네요. 기본적으로, 저희는 경계를 위에 추가할지, 아니면 캐싱을 사용할지 선택해야 합니다. 우선 간단하게 로딩 tsx 파일을 여기 추가해 보겠습니다.20:59

그리고 여기 로딩 페이지를 추가하고, 보기 좋은 스켈레톤 UI도 적용해 보겠습니다.21:13

꽤 괜찮게 오류도 해결되었는데, 기다리는 동안 이 페이지에서 아무런 유용한 일도 일어나지 않고 검색조차 할 수 없네요. 캐시 컴포넌트 때문에 그런 것 같기도 하고, 동적으로…21:21

정적과 동적의 관계는 마치 저울과 같아서, 우리에게 페이지에 어느 정도의 정적을 넣을지 결정하는 몫이 있습니다. 그래서 이 페이지를 좀 더 정적으로 변경하고, 방금 전에 삭제했던 로딩 tsx도 다시 제거한 다음, 이전에 배운 패턴들을 활용해 보겠습니다.21:31

이 데이터를 컴포넌트 안으로 밀어넣고 UI와 함께 위치시키는 거예요. 그래서 이걸 제 반응형 카테고리 필터 쪽으로 옮기는 거죠.21:46

저는 반응형 디자인 때문에 두 개 가지고 있어요. 여기 그냥 추가해도 될 것 같아요.21:54

앗, 실수했네요. 그리고 이걸 불러오세요. 이제 이 prop은 필요 없어요.22:01

실제로, 제 컴포넌트가 더 조합 가능하게 되고 있어요. 그리고 이걸 서스펜드하는 대신, 그냥 useCache 지시어를 추가하면 충분할 것 같아요. 그래서 제가 어떤 곳에서 프로미스를 해결하고 있는지 더 자세히 생각해야 한다는 걸 알아차리시겠어요.22:06

실제로 이걸 통해서 제 컴포넌트 아키텍처를 개선하게 될 것 같아요. 이걸 서스펜드할 필요 없이, 그냥 여기 정적 셸에 포함될 거예요.22:20

제품 목록은 계속 신선하게 유지하고 싶어서 매번 다시 로드할 수 있도록 할게요. 반면에 하단의 카테고리는 이 또한 캐시하고 싶습니다.22:31

자, 그럼 푸터로 넘어가 보도록 하겠습니다. 제가 여기 도넛 패턴을 사용하고 있어서, 이 부분은 UI 내부, 즉 인터랙티브한 UI의 일부임에도 불구하고 캐시할 수 있습니다.22:42

이건 괜찮아요. 그 패턴은 컴포지션에도 좋았을 뿐만 아니라 캐싱에도 도움이 됐거든요. 그런데 거기에 또 다른 오류가 있는 것 같아요. 한번 살펴봐야겠네요.22:54

아직도 이 에러가 남아있네요. 사실 이건 검색 브랜드 때문이에요. 검색 브랜드는 우리가 알고 있듯이 동적인 API이기 때문에 캐싱할 수 없지만, 더 깊숙이 해결해서 UI를 더 드러내고 정적으로 만들 수 있어요.23:07

이걸 아래로 옮겨서, 프로덕트 리스트에 프로미스로 전달해 봅시다. 여기서는 프로미스 타입으로 만들게요.23:18

제품 목록 안에서 해결해 봅시다. 여기서 해결된 검색 프로미스를 여기저기 사용하고요. 그리고 이게 여기 일시 중단되어 있기 때문에 에러는 사라질 겁니다.23:30

다시 로드하고 보니, 여기 로드되는 것은 제가 동적으로 설정한 특정 부분뿐입니다.23:40

다른 모든 부분은 캐시할 수 있고, 그렇다는 건 제가 배너와 상호작용하거나 심지어 검색도 할 수 있다는 뜻입니다. 그 부분은 이미 미리 렌더링되었으니까요. 좋습니다, 이제 마지막 페이지인 상품 페이지를 진행해 보겠습니다. 음, 23:48

가장 어렵고 중요한 문제인데, 지금 상태가 정말 심각한 것 같습니다. 분명히 전자상거래 플랫폼에 매우 중요한 부분인데, 이 부분도 함께 바로잡아야 할 것 같습니다.24:02

자, 여기 상품 페이지가 있습니다. 재사용 가능한 콘텐츠 부분은 캐싱을 시작해 보겠습니다. 예를 들어 상품 자체를 캐싱하고, 여기에도 캐시를 추가해서 캐시로 표시하면 괜찮을 것 같습니다.24:15

네, 여기 서스펜스 경계를 제거할 수 있습니다. 그리고 이제 요청이 있을 때마다 상품 상세 정보가 새로고침되지 않도록 할 수 있습니다. 같은 방식으로 처리해 봅시다. 캐시를 사용하고, 캐시로 표시해 보겠습니다.24:29

그것 역시 작동할지 확인해 봐야겠네요. 실제로는 다른 오류가 발생했는데, 캐시된 세그먼트 내에서 동적 API를 사용하려고 한다고 알려주고 있습니다. 그리고 맞는 말입니다. '저장된 상품' 버튼을 사용하고 있으니까요.24:45

저건 클릭해서 저장 상태를 변경하지 않네요. 그럼 어떻게 해야 할까요? 도넛 패턴을 다시 사용할 수 있을 것 같아요.24:57

사실, 동적 세그먼트도 캐시 세그먼트에 슬롯인 할 수 있어요. 이전과 마찬가지로 교차해서 넣는 방식이죠, 하지만 캐시를 사용해서요. 그래서 이것도 꽤 멋지네요. 자, 그럼 여기 자식 요소를 추가해볼게요.25:06

이렇게 하면 오류가 사라지고, 이 동적인 페이지 영역 하나를 감싸서 사용할 수 있습니다. suspense 경계를 제거하고, 해당 동적 영역에 아주 작은 북마크 UI를 추가할 수 있을 것 같습니다.25:20

자, 이제 어떻게 보이는지 한번 살펴볼까요? 주목해주세요, 거의 전체 UI가 사용 가능한데, 제가 이 작은 부분 하나만 동적으로 변경되어 있습니다. 괜찮습니다.25:38

다른 모든 요소는 그대로입니다. 그리고 리뷰는 계속 동적으로 유지해두죠. 신선하게 유지할 수 있으니까요.25:47

아직 하나의 오류가 더 있습니다. 간단하게 처리해볼까요. 역시 params 관련된 문제입니다.25:53

선택을 해야 하는데, 로딩 폴백을 추가할지 캐시할지 도움을 받고 있어요. 여기서는 그냥 정적 파라미터를 생성해서 사용하겠습니다. 사용 사례와 데이터셋에 따라 달라질 수 있습니다.25:58

하지만 이번 경우에는 미리 렌더링된, 미리 정의된 페이지 몇 개를 추가한 다음에 사용자 생성 후에 나머지는 캐싱할 거예요. 그러면 여기 있는 오류가 사라질 겁니다.26:10

리팩토링은 이제 끝낸 것 같아요. 배포된 버전이 어떻게 생겼는지 한번 살펴볼까요? 그래서 이걸 Vercel에 배포했어요.26:21

그리고 제가 의도적으로 데이터 로딩 속도를 늦춘 부분도 기억해주세요. 그래도 이 페이지를 처음 로드할 때, 모든 것이 이미 준비되어 있는 상태입니다, 그렇죠?26:31

여기서 중요한 건 할인이나 For You 같은 몇 가지 동적 세그먼트뿐이에요. Browse All에서도 모든 UI가 이미 로드되어 있고요. 제품 자체를 볼 때는 바로 로딩되는 것처럼 느껴지죠?26:41

그리고 이 모든 캐시 세그먼트들이 부분적인 사전 로딩과 함께 정적 셸에 포함될 것이라는 점을 다시 한번 기억해 주십시오. 새로운 Next.js 클라이언트 라우터의 개선된 프리페칭을 통해 미리 가져올 수 있습니다. 즉, 모든 탐색이 매우 빠르다고 느껴질 겁니다.26:54

네, 캐시 컴포넌트를 활용하여 요약한다면, 더 이상 정적과 동적이라는 구분이 필요하지 않다는 말씀이신가요.27:12

그리고 우리는 동적인 API를 피하거나 동적인 콘텐츠를 저해할 필요가 없습니다.27:20

제가 보여드린 것처럼, 단 하나의 캐시 히트를 위해 여러 데이터 파티션 전략을 활용한 복잡한 꼼수와 우회적인 방법을 생략하고 진행할 수 있습니다.27:28

최신 Next.js에서는 동적과 정적 표현 방식이 축(scale)으로 존재하며, 저희 앱에서 얼마나 정적 표현 방식을 사용할지 결정하게 됩니다.27:36

음, 특정 패턴을 따르면 기본적으로 성능이 뛰어나고 재사용 가능하며 확장 가능한 하나의 정신 모델을 가질 수 있습니다. 그럼 다시 슬라이드로 돌아가서, 혹시 아직 저희에게 깊은 인상을 받지 못하셨더라도요.27:43

이것은 Lighthouse 점수와 관련된 내용입니다. 저는 Versell Speed Insights를 사용하여 몇 가지 데이터를 수집했습니다. 가장 중요한 페이지들, 즉 홈페이지, 제품 페이지, 제품 목록 페이지 모두 100점을 받았습니다. 이 페이지들은 매우 역동적이지만요. 이제 최종적으로 패턴을 요약해 보겠습니다.27:55

이를 통해 Next.js 애플리케이션에서 확장성과 성능을 보장하고 최신 혁신을 활용하며 이러한 점수를 얻을 수 있을 것입니다.28:10

먼저, 컴포넌트 트리 깊숙한 곳에서 프로미스를 해결하고 React 캐시를 사용하여 컴포넌트 내부에서 데이터를 로컬로 가져와 아티텍처를 개선할 수 있습니다. 이를 통해 중복 작업을 피할 수 있습니다. 또한, 컨텍스트 프로바이더와 React use를 함께 사용하여 클라이언트 컴포넌트로 과도한 프로프 전달을 방지할 수 있습니다.28:18

두 번째로, 도너 패턴을 활용하여 클라이언트 컴포넌트를 구성함으로써 클라이언트 측 JavaScript 코드를 줄이고, 관심사 분리를 명확하게 유지하며, 컴포넌트 재사용성을 높일 수 있습니다. 또한 이 패턴을 통해 추후 구성된 서버 컴포넌트를 캐싱하는 데에도 도움이 될 것입니다.28:35

마침내 캐시를 활용하여 페이지 컴포넌트나 함수를 통해 미리 렌더링할 수 있게 되었습니다. 이를 통해 불필요한 프로세스를 제거하고 성능과 SEO를 향상시킬 수 있습니다. 또한, 부분적으로 미리 렌더링하여 앱의 특정 구간을 정적으로 렌더링할 수 있습니다. 만약 저희 콘텐츠가...28:50

정말로 역동적이기 때문에, 적절한 로딩 폴백을 통해 일시 중단할 수 있습니다. 그리고 이 모든 것이 연결되어 있다는 점을 기억해야 합니다. 아키텍처가 좋을수록 구성하기 쉽고, 최상의 결과를 얻을 수 있도록 캐시하고 미리 렌더링하기도 용이할 것입니다.29:02

예를 들어, 트리 내부의 동적 API 문제를 더 깊이 해결하면 부분적으로 출력된 정적 쉘을 더 크게 만들 수 있습니다.29:15

그리고 이것으로, 애플리케이션 완성 버전의 레포지토리입니다. 제가 보여드리지 못한 내용들이 훨씬 많으니 확인해 보시는 게 좋을 것 같아요.29:22

QR 코드를 스캔하시면 제 소셜 미디어 계정과 저장소 주소를 함께 찾으실 수 있습니다. 사진을 찍어 직접 입력하는 번거로움 없이요. 네, 이것으로 제 발표는 마치겠습니다. 저를 초대해주신 NextGescom에 감사드립니다.29:29

AI Summary

Next.js 애플리케이션의 성능 최적화 및 아키텍처 개선을 위한 핵심 전략을 제시합니다. 기존 방식의 정적/동적 페이지 구분, 과도한 서스펜스 경계 사용, 복잡한 아키텍처 문제점을 해결하기 위해 useCache 훅을 활용한 부분적 캐싱, 도넛 패턴 적용, 데이터 로딩 전략 개선 등을 통해 성능 향상, 사용자 경험 개선, 그리고 간결하고 재사용 가능한 아키텍처 구축을 목표로 합니다. 또한, 컴포넌트 트리 내 프로미스 해결, React 캐시 활용, 도너 패턴 적용, 페이지 컴포넌트 캐싱 등을 통해 성능을 극대화하고 유지보수 용이성을 높이는 방법을 설명하며, 이러한 전략들이 유기적으로 연결되어 Next.js 애플리케이션의 전반적인 성능을 향상시키는 데 기여합니다.

Key Highlights

  • Next.js `useCache` 훅을 활용하여 컴포넌트를 캐싱하고, 부분적인 캐싱 전략을 적용하여 성능을 최적화합니다.
  • 서스펜스 경계를 제거하고, UI 렌더링 지연을 줄이며, 불필요한 UI 렌더링을 방지합니다.
  • 데이터 로딩 전략을 컴포넌트 내부로 밀어넣어 UI와 데이터 로딩을 분리하고, 컴포넌트의 재사용성과 조합성을 향상시킵니다.
  • 도넛 패턴을 적용하여 캐싱과 동적 콘텐츠의 조화를 이루고, 정적/동적 페이지 구분을 벗어나 유연하게 페이지 콘텐츠를 관리합니다.
  • 컴포넌트 트리 내 프로미스 해결, React 캐시 활용, 도너 패턴 적용 등을 통해 성능을 극대화하고 유지보수 용이성을 높입니다.

Related Videos