
https://nextjs.org/learn/dashboard-app/streaming
스트리밍의 정의와 언제 사용하는지
loading.tsx와 Suspense로 스트리밍 구현 방법
loading skeletons가 무엇인지
route 그룹이 뭐인지와, 언제 사용하는지
앱에서 Suspense boundaries를 어디에 위치 시킬지
루트를 작은 chunk로 분리하고, 준비가 되면 서버 => 클라이언트로 스트림을 진행하게 하는 데이터 전달 기술이다.

스트리밍을 통해, 느린 데이터 요청을 막을 수 있다.
이것은 사용자에게 어느 UI가 보여지기 전에 모든 데이터를 기다리는 것 없이 페이지의 부분을 보고, 상호작용할 수 있게 한다.

스트리밍은 리액트 컴포넌트 모델에서 잘 작동하는데, 각 컴포넌트가 청크로 간주될 수 있기 때문이다.
다음은 스트리밍을 구현하는 2가지 방법이다
1. page 레벨에서 loading.tsx 파일로
2. 특정 컴포넌트에 대해, 'Suspense' 컴포넌트로
/app/dashboard 폴더에서, loading.tsx를 생성한다.
export default function Loading() {
return <div>Loading...</div>;
}
http://localhost:3000/dashboard 새로고침 시 다음과 같은 화면을 볼 수 있다

다음과 같은 일이 일어난다.
1. loading.tsx는 Suspense의 최상단에 내장 된 특별한 Next.js 파일이다.
이것은 페이지 컨텐츠 로드 시 fallback UI 를 생성하고 대체해서 보여준다.
2. SideNav 태그가 정적이기 때문에, 즉시 보여진다. 사용자는 동적 컨텐츠가 로딩되는 동안 이 태그와 상호작용할 수 있다.
3. 사용자는 navigating awau 하기 전 페이지가 로딩을 끝내길 기다릴 필요가 없다.
이제 Loading... txt대신 로딩 스켈레톤을 보자
로딩 스켈레톤은 UI의 심플 버전이다
loading.tsx에 추가된 모든 UI는 정적 파일의 일부로 임베드 되고, 처음 보내진다.
그러면, 나머지 동적 컨텐츠가 서버 => 클라이언트로 스트림 된다.
loading.tsx 파일에 DashboardSkeleton 컴포넌트를 import 한다.
import DashboardSkeleton from '@/app/ui/skeletons';
export default function Loading() {
return <DashboardSkeleton />;
}

로딩 스켈레톤은 invoices, customers 페이지에도 적용된다.
loading.tsx가 /invoices/page.tsx, /customers/page.tsx 보다 높은 경로에 있기 때문에 적용되는 것이다.
이것은 Route Groups를 통해 바꿀 수 있다.
1. /(overview) 폴더를 dashboard 폴더 안에 만든다.
2. loading.tsx, page.tsx 파일을 그 안으로 옮긴다.

이제 loading.tsx 파일은 대시보드 오버뷰 페이지에서만 적용된다.
루트 그룹은 URL 경로 구조 영향 없이 논리적 그룹으로 파일 정렬을 도와준다.
괄호를 통해 새 폴더 생성 시, 이름은 url 경로에 포함되지 않을 것이다.
그래서, /dashboard/(overview)/page.tsx 는 /dashboard 가 된다.
여기선, 루트 그룹을 loading.tsx가 대시보드 오버뷰 페이지에만 적용되게 하고 있다.
그러나. 루트 그룹을 앱 분할 혹을 큰 앱을 위해 사용할 수 있다.
이제 전체 페이지를 스트리밍 했다.
하지만 리액트 서스펜스를 통해 특정 컴포넌트를 스트림 할 수 있다.
서스펜스는 특정 조건이 만족될 때까지 앱의 부분을 defer 렌더링하게 한다.
서스펜스로 동적 컴포넌트를 감쌀 수 있다.
그리고, 동적 컴포넌트가 로드될 동안 fallback 컴포넌트가 보여지도록 전달할 수 있다.
fetchRevenue( )는 전체 페이지를 느리게 하는 요청이다.
전체 페이지 블록 대신. 서스펜스를 이 컴포넌트만 스트림하고, 나머지의 페이지를 즉시 보여주기 위해 사용할 수 있다.
이러기 위해서는. 데이터 페치를 컴포넌트로 이동해야 한다.
1. fetchRevenue( )의 객체를 /dashboard/(overview)/page.tsx 에서 다 지운다.
2. 서스펜스를 리액트로부터 불러와 RevenueChart 를 감싼다.
3. RevenueChartSkeleton 컴포넌트로 전달할 수 있다.
4. 데이터를 페치하고 전달되는 prop을 제거하기 위해 RevenueChart 컴포넌트를 갱신한다.
이제 새로고침 시, 스켈레톤이 RevenueChart에 대해 보여지는 것과 반대로 대시보드 정보를 거의 즉시 볼 수 있다.
fetchLatestInvoices()를 LatestInvoices로 보낸다.
컴포넌트를 LatestInvoicesSkeleton 라는 fallback을 가진 Suspense로 감싼다.
이제 card 컴포넌트를 Suspense로 감싸야한다.
각 카등 대해 데이터를 페치할 수 있지만, 카드가 로드될 때 popping 효과를 초래할 수 있다.
이 문제를 해결하기 위해서는
더 stagger한 효과를 위해, 카드를 wrapper 컴포넌트를 통해 그룹 지을 수 있다.
이것은 정적 SideNav 컴포넌트가 먼저 보여지고, 카드가 보여질 것을 의미한다.
page.tsx 파일에서:
1. Card 컴포넌트를 지운다.
2. fetchCardData( )를 지운다
3. 새 wrapper 컴포넌트를 불러온다 : CardWrapper
4. 새 스켈레톤 컴포넌트를 불러온다 : CardsSkeleton
5. 서스펜스로 CardWrapper를 감싼다.
6. 파일을 /app/ui/dashboard/cards.tsx로 가서 fetchCardData()를 불러온다.
7. 그리고 CardWrapper 컴포넌트에서 호출한다.
새로고침 시 모든 카드가 동시에 로드 되는 것을 알 수 있다.
다른 컴포넌트에도 적용할 수 있다.
서스펜드 바운더리 배치는 몇 개에 의존한다.
1. 사용자가 스트림 경험을 어떻게 하게 할 것인지
2. 어떤 컨텐츠를 우선시할 것인지
3. 컴포넌트가 데이터 페칭에 의존하는지
대시보드 페이지를 보면, 다르게 한 것이 있는지?
정답
서스펜드 바운더리를 배치하는 위치는 앱에 따라 다를 것이다.
보통, 데이터를 하향식으로 fetch 하는게 좋고, 그 컴포넌트를 Suspense로 감싸야한
하지만 전체 혹은 섹션 스트리밍에 틀린 것은 없다 .