React 의 프레임워크인 NEXT 의 app router 부분을 공식문서를 읽어가며 이해해보도록 하겠습니다.
우선 가장 먼저 Routing 부분 부터 읽어보도록 하겠습니다.
Routing
해당 문서를 읽기 위해서는 아래의 용어들이 문서에서 사용되고 있다고 합니다.
Tree
트리는 계층구조를 시각화 하기 위한 도구입니다.
예를 들어 컴포넌트의 구조라던가 폴더 구조 등이 트리의 형태를 가지고 있습니다.
SubTree
서브트리는 트리의 부분적인 요소를 의미합니다.
Root
루트는 트리나 서브트리의 첫 노드를 의미합니다.
루트의 레이아웃 등이 있습니다.
Leaf
리프는 서브트리에서 더 이상 노드가 없을 때 리프라고 합니다.
URL path 의 마지막 세그먼트가 됩니다.
URL Segement
슬래쉬로 나누어진 URL 의 한 부분을 의미합니다.
URL Path
도메인의 뒤에 오는 URL 부분이라고 합니다.
세그먼트의 집합이라고도 합니다.
Next 에서는 13 버전부터 공유 레이아웃, 중첩 라우팅, 로딩 상태, 에러 핸들링 등을 지원하는
React Server Components 기반의 새로운 App Router 를 도입했습니다.
기본적으로 app 폴더 내에 있는 컴포넌트들은 React Server Components 입니다.
Next 는 파일 시스템 기반의 라우팅을 활용합니다.
폴더는 라우트를 정의하기 위해 사용합니다.
root 폴더 부터 page.js 를 포함하고 있는 leaf 폴더까지가 라우팅 경로가 됩니다.
Next 에서는 중첩된 경로에서 특정 동작을 가진 UI 를 만들기 위한 특수 파일 집합을 제공합니다.
폴더 내에 위와 같은 파일들을 가질 수 있다고 합니다.
leaf Segement의 경우에는 page.js 를 가지고 있을 것 같고 Segement 의 경우에는 layout.js 를 가지고 있을 수 있을 것 같습니다.
App router 의 중첩 경로에서 세그먼트의 구성 요소는 상위 세그먼트의 구성 요소 내부에 중첩된다고 합니다.
구조를 잡을 때 미리 구상해서 잡는게 좋을 것 같고 구조를 변경해야 하는 상황이 생길 때 주의해야 할 것 같습니다.
Defining Routes
Next 는 폴더를 통해 경로를 정의하는 파일 시스템 기반의 라우터를 사용합니다.
각 폴더는 URL 세그먼트에 매핑되는 라우트 세그먼트가 됩니다.
중접된 라우팅을 만들기 위해서는 중첩되는 폴더 구조를 통해 제작할 수 있습니다.
page.js 파일은 라우트 세그먼트에 공개적으로 액세스 할 수 있도록 사용됩니다.
page 파일의 경우에는 해당 경로의 유티크한 UI 를 보여줄 때 사용하고
layout 파일은 여러 경로에 공통적으로 사용되는 UI 를 보여줄 때 사용합니다.
Pages
page 파일의 경우에는 해당 경로의 유티크한 UI 를 보여줄 때 사용된다고 이야기했습니다.
폴더 구조를 위와 같이 설정했다면
"/" 로 접근했다면, app 폴더 하위의 page.js 가 UI 를 그리게 될 것이고
"/dashboard" 로 접근했다면 dashboard 폴더 하위의 page.js 가 UI 를 그리게 될 것입니다.
만약, app 폴더 하위에 layout 폴더가 존재한다면 중첩된 라우트 구조로 인해
dashboard 의 page.js 도 레이아웃으로 감싸진 컴포넌트가 그려지게 될 것입니다.
Layouts and Templates
layout 과 template 와 같은 특별한 파일은 라우트 사이에 공유된 UI 를 만들 때 사용합니다.
layout 은 여러 경로 사이를 공유하는 UI 를 만들 때 사용합니다.
네비게이션이 될 때 레이아웃은 상대를 유지하고 interactive 를 남게하고, 리렌더링을 하지 않는다고 합니다.
layout 컴포넌트는 props 로 children 을 받아 사용합니다.
Root Layout 은 app 디렉토리의 최상위 레벨에 적용되고 모든 라우트에 적용됩니다.
해당 레이아웃 파일은 필수적으로 존재해야 하고 html 과 body 태그를 가지고 있어야 합니다.
서버에서 반환된 초기 HTML 을 수정할 수 있습니다.
layout 파일의 경우에는 폴더 구조에 따라 중첩된 구조로 만들 수 있고 위 이미지의 형태로 구성됩니다.
부모 레이아웃과 하위 레이아웃 간에 데이터를 전달할 수는 없습니다.
그러나 동일한 데이터를 여러 번 가져올 수 있으며 React 는 성능에 영향을 주지 않고 요청을 자동으로 중복 제거합니다.
Building Your Application: Caching | Next.js (nextjs.org)
Building Your Application: Caching | Next.js
An overview of caching mechanisms in Next.js.
nextjs.org
Templates 는 하위 레이아웃이나 페이지를 감싼다는 관점에서는 레이아웃과 비슷합니다.
하지만, 레이아웃과 달리 네비게이션이 될 때 새로운 컴포넌트를 생성하게 됩니다.
만약 유저가 네비게이트를 할 경우 하위 요소들은 다시 마운트 되고 돔 요소는 다시 만들어 질 것이며
상태는 보존되지 않을 것입니다.
그리고, effect 들은 다시 동기화 될 것입니다.
네비게이션이 발동할 때 useEffect 를 다시 동기화 하고 싶을 때
네비게이션이 발동할 때 하위 컴포넌트들의 상태를 초기화하고 싶을 때
템플릿을 사용할 수 있습니다.
템플릿의 경우 레이아웃과 children 사이에 그려지게 됩니다.
key 로 routeParam 을 활용함으로써 네비게이션 시 다시 마운트 됩니다.
Linking and Navigating
Next 에서는 총 4 가지 방법의 네비게이션 방법을 제공합니다.
<Link> 컴포넌트
useRouter hook ( Client Components )
redirect function ( Server Components )
natvie History API
<Link> 컴포넌트
<Link> 는 HTML 의 <a> 태그를 확장한 빌트인 컴포넌트 입니다.
prefetching 과 같은 기능들을 제공하고 있습니다.
Scrolling to an id 라는 기능을 추가적으로 제공하는데요.
Next 의 App Router 는 기본적으로 새로운 경로에 접근했을 경우 스크롤을 최상단으로 이동시키고
backwards 와 forwards 네비게이션의 경우 스크롤 위치를 유지하는 기능을 제공합니다.
만약, 특정한 id 로 네비게이션을 하고 싶다면
href 의 URL 에 # hash link 를 추가함으로써 스크롤을 해당 id 값으로 이동시킬 수 있습니다.
만약, 위와 같은 기본적인 스크롤 기능을 활용하고 싶지 않을 경우 Link 컴포넌트의 props 로 scroll 에 false 를 넣거나
useRouter 훅을 사용한다면, 해당 훅 안의 메서드의 옵션 파라미터에 scroll 값을 false 로 둡니다.
useRotuer hook ( Client Components )
Functions: useRouter | Next.js (nextjs.org)
Functions: useRouter | Next.js
API reference for the useRouter hook.
nextjs.org
redirect function ( Server Components )
Functions: redirect | Next.js (nextjs.org)
Functions: redirect | Next.js
API Reference for the redirect function.
nextjs.org
native History API
Next 에서는 window.history.pushState 와 window.history.replaceState 메서드를 제공한다.
이 기능들은 page reloading 없이 작동한다고 한다.
여기서 page reloading 은 hard refresh 를 의미하는 것 같다. 실제 리렌더링 과정은 일어나게 된다
pushState 와 replaceState 호출은 Next 의 Router 에 통합되므로
usePathname 과 동기화하고 SearchParams 를 활용할 수 있다고 한다.
How Routing and Navigation Works ?
App Router 는 라우팅과 네비게이션에 복합적인 접근 방법을 사용한다고 한다.
서버에서는 라우트의 세그먼트에 따라 자동적으로 코드 스플리팅이 적용이 된다.
클라이언트에서는 라우트의 세그먼트를 프리패치하고 캐시한다고 한다.
유저가 새로운 경로로 네비게이션 할 때 브라우저가 페이지를 리로드 하지 않고 경로 세그먼트에 따른 변화만 다시 렌더링하여
탐색 경험과 성능을 향상시킨다고 한다.
클라이언트에서는 dynamic 을 통해서 코드 스플리팅을 구현할 수 있을 것 같다.
프리패칭은 해당 페이지에 유저가 방문하기 전에 백그라운드에서 프리로드 하는 것을 의미한다.
<Link> 컴포넌트의 경우에는 유저의 뷰포트안에 보여지는 내용들을 자동적으로 프리패칭 한다고 한다.
프리패칭은 페이지가 처음 로드되거나 스크롤을 통해 볼 수 있을 때 발생한다고 한다.
<Link> 의 기본 프리패치 동작은 loading.js 를 사용하는 용도에 따라 다르다고 한다.
첫 번째 loading.js 파일까지 구성 요소의 렌더링된 트리 아래로 내려간 공유 레이아웃만 30초간 캐싱되고 프리패치 된다고 한다.
이것은 전체 동적 경로를 가져오는 데 드는 비용이 절감되며 사용자에게 더 나은 시각적 피드백을 위해
즉각적인 로딩 상태를 보여줄 수 있다고 한다.
추후 실습을 통해 테스트 해봐야 할 것 같음
Next 는 Router Cache 라고 하는 in-memory client-side cache 를 사용한다고 한다.
유저가 앱을 네비게이션을 통해 돌아다니는 동안 프리패치된 경로 세그먼트와 방문한 경로의
React Server Component 의 페이로드가 캐시에 저장됩니다.
즉, 내비게이션에서는 서버에 새로운 요청을 하는 대신 캐시를 최대한 재사용하여
요청 및 전송되는 데이터 수를 줄여 성능을 향상시킵니다.
네비게이션에 의해 라우터의 세그먼트가 바뀔 때 클라이언트에서는 리렌더링이 일어나게 됩니다.
그리고 세그먼트의 공유되는 부분은 유지되게 됩니다. 이를 Partial Rendering 이라고 합니다.
만약, 위와 같이 /dashboard/settings 와 /dashboard/analytics 사이에 네비게이션이 발생하게 된다면,
settings 와 analytics 페이지는 리렌더링 될 것입니다. 하지만, dashboard 의 레이아웃은 유지되게 될 것입니다.
해당 경로의 변경이 템플릿을 사용했을 때는 리렌더링이 발생하는지 테스트가 필요할 것 같음
브라우저는 페이지 사이를 네비게이팅 할 때 하드 네비게이션을 사용합니다.
Next 의 App Router 는 페이지 사이에 소프트 네비게이션을 지원합니다.
변경되는 세그먼트만 렌더링하면 전송되는 데이터의 양과 실행 시간이 줄어들어 성능이 향상됩니다.
Next 는 기본적으로 backwards 와 forwards navigation 을 할 때 스크롤의 위치를 유지하려 합니다.
그리고, Router Cache 안에 있는 라우트 세그먼트를 재사용합니다.
Error Handling
에러는 두 가지 카테고리로 나눌 수 있다고 합니다.
expected errors
uncaught exceptions
Handling Expected Errors
Handling Expected Errors from Server Actions
서버 액션의 상태를 관리하기 위해서는 useFormState 를 사용함으로써 try~catch 블록의 사용을 피하면서 예외가 아닌 반환 값으로 모델링 한다고 합니다.
useFormState 는 form 액션의 결과에 기반하여 상태를 업데이트 할 수 있게 해주는 Hook 입니다.
useFormState 에 server Action 을 넣음으로써 form submit 이벤트가 발동했을 때 서버 액션을 작동시키고 상태값을 돌려받는 형식으로 보입니다.
Handling Expected Errors from Server Components
데이터를 패칭하는 서버 컴포넌트 내에서 에러가 발생했을 경우, 응답값을 통해 에러 메세지를 렌더링 하거나 redirect 를 실행할 수 있다고 합니다.
Uncaught Exceptions
잡히지 않은 예외의 경우에는 애플리케이션의 정상적인 흐름 중에 발생해서는 안 되는 버그나 문제를 나타내는 예기치 못한 오류입니다. 이것은 throwing errors 로 핸들링 되어야 하고 error boundaries 를 통해 캐칭될 것입니다.
에러에 대한 처리 방법을 하위의 세 가지 방법으로 알려주고 있는데요.
Common : 루트 레이아웃에서 하위에서 잡히지 않은 오류의 경우 error.js 에서 핸들링 합니다.
Optional : 중첩된 error.js 파일 구조를 통해 잡히지 않은 오류의 세분화된 핸들링을 합니다.
Uncommon : 루트 레이아웃에 있는 global-error.js 를 통해 잡히지 않은 에러를 핸들링 합니다.
Using Error Boundaries
Next 는 잡히지 않은 예오를 처리하기 위해 error boundaries 를 사용합니다.
에러 바운더리는 하위 컴포넌트에서 발생된 에러를 잡고 오류가 발생한 컴포넌트 트리를 대신해 fallback UI 를 보여줍니다.
라우트 세그먼트 내에 error.tsx 파일을 추가하고 React Components 를 export 하는 방식을 통해 에러 바운더리를 구현할 수 있습니다.
에러 바운더리는 Client Components 내부에 있어야 합니다.
만약 부모의 에러 바운더리까지 에러를 올리고 싶다면 error 컴포넌트를 렌더링 할 때 throw 하는 방식을 통해 올릴 수 있습니다.
Handling Global Errors
흔하지 않은 에러에 대해 root layout 에서 global-error.js 를 통해 에러를 핸들링 할 수 있다고 합니다. global error UI 의 경우 개별적인 html 과 body 태그를 넣어야 하고 해당 에러 컴포넌트가 작동될 때 root layout 과 template 가 대체되서 작동될 것이라고 합니다.
Loading UI and Streaming
loading.js 파일은 React Suspense 를 활용해 로딩 UI 를 만들어줍니다.
콘텐츠가 로드되는 동안 서버에서 즉시 로드 상태를 표시할 수 있고 렌더링이 완료되면 새 콘텐츠가 자동으로 스왑된다고 합니다.
Instant Loading States
instant loading state 는 탐색 즉시 보여지는 fallback UI 입니다.
스켈레톤 및 스피너와 같은 로딩 표시기나 표지 사진, 제목 등 의미있는 향후 화면의 일부를 미리 렌더링 할 수 있습니다.
이는 사용자에게 앱이 반응하고 있다는 것을 이해시킬 수 있고 더 나은 UX 를 제공합니다.
동일한 폴더에서 loading.js 는 layout.js 내에 위치하게 됩니다.
이것은 자동으로 page.js 파일을 감싸고 모든 하위 파일을 <Suspense> 경계에 자동으로 감싸줍니다.
server-centric routing 일지라도 네비게이션은 즉시 이루어집니다.
네비게이션은 중단될 수 있으므로 경로 변경 시 네비게이션 되기 전에 경로의 내용이 완전히 로드될 때까지 기다릴 필요가 없습니다.
공유 레이아웃은 새 경로 세그먼트가 로드되는 동안에도 상호작용될 수 있게 남아있습니다.
Streaming with Suspense
loading.js 외에도 UI 컴포넌트를 위한 Suspense Boundary 를 만들 수 있습니다.
앱 라우터는 Node.js 와 Edge runtimes 에 Suspense 를 사용한 Streaming 을 지원합니다.
What is Streaming ?
React 와 Next 에서 스트리밍이 어떻게 동작하는지를 배우면 SSR 과 SSR 의 한계에 대해 이해할 수 있습니다.
SSR 을 사용하면 유저가 페이지와 인터렉션하기 전에 하위의 스탭을 거치게 됩니다.
1. 주어진 페이지에 대한 모든 데이터를 서버에 가져옵니다.
2. 서버는 페이지의 HTML 을 렌더링합니다.
3. 페이지의 HTML, CSS 및 자바스크립트가 클라이언트에 전송됩니다.
4. 생성된 HTML 과 CSS 를 사용해 인터렉티브 하지 않은 UI 를 표시합니다.
5. 리액트는 hydrates 를 통해 UI 를 인터렉티브 하게 합니다.
이 스탭은 순차적이고 블로킹될 수 있습니다.
서버는 모든 데이터를 가져온 후에만 페이지에 대한 HTML 을 렌더링 할 수 있습니다.
또한 React 는 페이지의 모든 구성 요소에 대한 코드를 로드 한 후에 hydrate 를 진행할 수 있습니다.
SSR 은 가능한 한 빨리 사용자에게 논인터렉티브한 UI 를 제공함으로써 인식된 로딩 성능을 개선하는데 도움이 됩니다.
페이지를 최대한 빨리 노출해서 사용자에게 로딩되고 있다는 사실을 보여줄 수 있다는 말인 것 같습니다.
그러나 페이지를 사용자에게 보여주기 전에 서버의 모든 데이터 패칭을 완료해야하므로 느릴 수 있습니다.
스트리밍을 통해 페이지의 HTML 을 더 작은 청크로 분해하여 서버에서 클라이언트로 점진적으로 전송할 수 있습니다.
UI 를 렌더링하기 전에 모든 데이터가 로드될 때 까지 기다리지 않고 페이지의 일부를 더 빨리 표시할 수 있습니다.
스트리밍은 각 컴포넌트는 청크로 간주될 수 있기 때문에 리액트의 구성 요소 모델과 잘 작동합니다.
우선순위가 높거나 데이터에 의존하지 않는 구성 요소는 먼저 전송할 수 있으며 일찍 하이드레이션을 시작할 수 있습니다.
우선순위가 낮은 구성 요소는 데이터를 가져온 후 동일한 서버 요청으로 전송할 수 있습니다.
스트리밍은 특히 긴 데이터 요청으로 인해 페이지 렌더링이 차단되는 것을 방지하고자 할 때 유용합니다.
이로 인해 TTFB 및 FCP 를 줄일 수 있고 느린 장치에서 TTI 를 게선하는 데 도움이 됩니다.
Streaming Server Rendering - 서버에서 클라이언트로 HTML 을 점진적으로 렌더링 합니다.
Selective Hydration - 리액트는 사용자 인터렉션을 기반으로 인터랙티브를 먼저 만들 컴포넌트의 우선순위를 결정합니다.
Route Groups
app 디렉토리에서 중첩된 폴더 구조는 보통 URL 경로에 매핑됩니다.
그러나 folder 가 URL 경로에 포함되는 것을 방지하기 위해 폴더를 Route Group 으로 구성할 수 있습니다.
Rotue Groups 는 (forlderName) 으로 구성할 수 있습니다.
Organize routes without affecting the URL path
URL에 영향을 주지 않는 라우트를 구성하려면 관련 경로를 함께 묶는 그룹을 만들라고 합니다.
괄호 안의 폴더는 경로에서 생략됩니다.
이 케이스에서 marking 과 shop 내에는 동일한 URL 계층을 가지게 됩니다.
각 그룹에 layout.js 파일을 둬 다른 레이아웃을 구성할 수 있습니다.
Creating multiple root layouts
위와 같은 방식을 통해 여러 개의 root layout 을 구성할 수 있습니다.
top-level 의 layout.js 파일을 제거하고 각 라우트 그룹마다 layout.js 를 추가함으로써 각 라우트 그룹마다 root layout 을 구성할 수 있습니다.
이 기능은 애플리케이션을 완전히 다른 UI 또는 환경을 가진 섹션으로 분할하는 데 유용합니다.
<html> 과 <body> 태그는 각 루트 레이아웃마다 필요합니다.
route group 은 조직화 외에 특별한 의미를 가지지 않고 URL 경로에는 영향을 미치지 않습니다.
라우트 그룹 내에 포함된 경로는 다른 경로의 동일한 URL path 와 겹쳐서는 안됩니다. (오류 유발)
만약 top-level 의 레이아웃 없이 여러 개의 루트 레이아웃을 사용한다면 최상단 page.js 역시 라우트 그룹 내에 포함되어야 합니다.
여러 루트 레이아웃을 탐색하면 client-side 내비게이션이 아닌 풀 페이치 로드를 유발하기에 주의해야 합니다.
'NEXT 공부' 카테고리의 다른 글
컴포넌트의 생성과 작동 원리 (0) | 2022.09.14 |
---|---|
JSX (0) | 2022.09.13 |
Virtual DOM (1) | 2022.09.13 |
React의 작동 원리 (1) | 2022.09.13 |
React의 설치와 실행 (0) | 2022.07.30 |