프로젝트를 진행하면서 Loading, Error 처리는 어떻게 해야 좋은지 또는 어떤 방식으로 적용이 되는지를 기록하려고 글을 쓰게 되었다.
next.js 14 app router 에서는 page router와는 달리 컴포넌트 단위로 Streaming을 할 수 있게 되었다. 즉 다시 말하면 전체 페이지가 로드될 때까지 기다릴 필요가 없고 데이터가 필요하지 않은 페이지 부분을 즉시 렌더링하고 데이터를 가져오는 페이지 부분에 대한 로딩 상태를 표시할 수 있다는 의미이다.
우선 Streaming을 이해하려면 React, Next.js에서 SSR(서버 측 렌더링)과 그 제한 사항을 이해해야한다.
SSR을 사용한다면 사용자가 페이지를 보고 상호 작용 하기 전에 완료해야하는 단계가 있다.
1. 특정 페이지의 모든 데이터를 서버에서 가져온다.
2. 서버에서 페이지의 HTML을 렌더링한다.
3. HTML, CSS, JS가 클라이언트로 전송된다.
4. 사용자가 화면을 볼 수 있도록 클라이언트에서 화면이 그려지게 된다. (pre-rendering)
5. 마지막으로 hydrate 과정을 거쳐 사용자가 직접적으로 페이지와 상호작용 할 수 있게 된다.!
SSR의 단계는 순차적이고 모든 데이터를 가져온 후에만 서버가 페이지의 HTML을 렌더링 가능하고, 클라이언트에서 페이지의 모든 구성 요소에 대한 코드가 로드된 후에만 UI에 hyrate가 가능하게 되어서 사용자에게 페이지를 가능한 빨리 보여주어 pre-rendering 할 수 있게 된다.
그렇지만 SSR 동작 단계를 보면 사용자에게 표시되기 전에 서버에서 모든 데이터를 가져와야 페이지를 볼 수 있게 되기때문에 기다리는 시간이 있기 마련이다.
스트리밍이란 페이지의 HTML을 더 작은 청크로 나누고 점진적으로 해당 청크를 서버에서 클라이언트로 보내는 기법이다.
이를 통해 UI가 렌더링되기 전에 모든 데이터를 가져오지 않아도 기다리지 않고 부분적으로 페이지의 일부를 더 빨리 표시할 수 있다.
app router에서는 loading.tsx 파일을 만들고 React에서 제공하는 Suspense를 사용하여 로딩 UI를 만들 수 있다. loading.tsx + Suspense 조합을 사용하면 경로 세그먼트의 콘텐츠가 로드되는 동안 서버에서 즉시 로드 상태를 표시하고 렌더링이 완료되면 page.tsx에 담겨있는 콘텐츠로 자동 교체된다.
loading을 보여주기 위해서는 단순히 경로 세그먼트의 loading.tsx 파일을 아래 처럼 추가하면 된다.
In the same folder, loading.js will be nested inside layout.js. It will automatically wrap the page.js file and any children below in a Suspense boundary.
동작 원리공식문서에 의하면 같은 폴더에 loading 파일은 layout 파일 안에 중첩되고 page 파일 그 아래의 모든 하위 항목을 Suspense 경계에 자동으로 래핑한다.
풀어서 얘기하면 loading
파일은 layout
파일 내부에서 사용되고, loading
파일은 자동으로 page 파일과 그 하위 자식들을 Suspense로 감싸게 된다는 의미이다.
여기서 잠깐! 그렇다면 layout과 page 파일 중에 어떤 것이 더 상위 계층이냐 라고 궁금해할 수 있는데
정답은 바로 layout이 page보다 상위 계층이라고 말할 수 있습니다.
즉 layout 내부에 page 가 있다면 그 page를 Suspense fallback UI인 loading 으로 대체해서 보여준다.
선택 버튼을 누르게 되면 예약되기 페이지가 렌더링 되기 전에 Loading 화면을 볼 수 있다.
전체적으로 사용 되었을 때는 data가 필요한 UI 뿐만 아니라 필요 없는 부분까지 다 로딩처리가 되고 있다. 만약 부분적으로 Data가 필요한 UI에만 사용하고 싶다면 해당 컴포넌트에 Suspense를 수동적으로 사용하면 된다.
예약하기 TopBar인 부분을 사용자가 먼저 보게 만들고 TicketList 컴포넌트만 로딩을 하려면 아래처럼 Suspense를 사용하면 된다.
사용자 경험 측면에서 보았을 때는 이렇게 부분적으로 Loading UI를 보여주는 방법이 더 좋다고 생각한다.
const Reservation = () => {
return (
<div>
<TopBar title="예약하기" />
<Container>
<Suspense fallback={<Loading />}>
<TicketList />
</Suspense>
</Container>
</div>
)
}
export default Reservation
next가 처음 렌더링 할 때는 서버에서 렌더링 후 클라이언트로 보내주기 때문에 처음에는 로딩이 보이지 않는다. 즉 새로고침 시에는 로딩이 보이지 않는다.
loading 파일은 클라이언트 라우팅 때만 적용되니 잘 알아보고 사용해야 한다.
https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming
https://nextjs.org/docs/app/building-your-application/routing/error-handling