NEXT.JS 13.4 문서 - 2(Routing - 1)

박정훈·2023년 5월 29일
3

Next.js

목록 보기
5/5

App 디렉토리

13버전부터 Next.js는 React Server Components가 내장된 새로운 App Router를 지원합니다. 이 친구는 layout을 공유할 수 있고, 라우팅 중첩, 로딩 상태, 에러 핸들링 등등을 지원합니다.

App Router는 app이라는 이름의 디렉토리 안에서 동작합니다. app 디렉토리는 pages 디렉토리와 함께 동작하며 점진적으로 채택이 가능합니다.

기본적으로, app 디렉토리 내부의 컴포넌트는 React Server Component입니다. 😮.

File Conventions

  • page.js : 접근 가능한 경로를 만들고 라우터의 유니크한 UI를 만듭니다.
    • route.js : 라우트의 server-side API endpoints를 만듭니다.
  • layout.js : segment와 이들의 자식들이 공유하는 UI를 만듭니다. layout은 page 또는 자식 segment를 감쌉니다.
    • template.js: 새 컴포넌트 인스턴스가 네비게이션시 마운트된다는 점을 제외하면 layout.js와 유사합니다.(??)
  • loading.js : segment와 이들의 자식을 위해 loading UI를 만듭니다. loading.js는 page또는 자식 segment를 React Suspense Boundary로 감쌉니다. 그들이 로드상태일때 loading UI를 보여줍니다.
  • error.js : segment와 이들의 자식을 위해 error UI를 만듭니다. error.js는 page또는 자식 segment를 React Error Boundary로 감쌉니다. error가 잡히면 error UI를 보여줍니다.
    • global-error.js : error.js와 비슷하지만, 특히 루트 레이아웃.js의 오류를 포착하기 위한 것입니다.
  • not-found.js : route segment나 URL이 어떠한 route와도 매치되지 않으면 보여줄 UI를 만듭니다.

Component Hierarchy

next.js Component Hierarchy

Server-Centric Routing with Client-side Navigation

클라이언트 측 라우팅을 사용하는 페이지 디렉토리와 달리, 앱 라우터는 서버 중심 라우팅을 사용하여 서버 컴포넌트와 서버의 data fetching과 일치시킵니다. 서버 중심 라우팅에서는 클라이언트가 route map을 다운로드할 필요가 없으며 서버 컴포넌트에 대한 동일한 요청을 사용하여 경로를 검색할 수 있습니다. 이 최적화는 모든 Application에 유용하지만 경로가 많은 Application에에 더 큰 영향을 미칩니다.

라우팅은 서버 중심이지만, 라우터는 single-page Application의 동작과 유사한 Link component로 클라이언트측 네비게이션을 사용합니다. 이것은 사용자가 새 경로로 이동할 때 브라우저가 페이지를 다시 로드하지 않는다는 것을 의미합니다. 대신 URL이 업데이트되고 Next.js는 변경되는 세그먼트만 렌더링합니다.

또한 사용자가 앱을 탐색할 때 라우터는 React Server 구성 요소 페이로드의 결과를 인메모리 클라이언트 측 캐시에 저장합니다. 캐시는 경로 세그먼트별로 분할되므로 모든 수준에서 무효화할 수 있으며 React의 동시 렌더링 간에 일관성을 보장합니다. 이는 특정한 경우 이전에 가져온 세그먼트의 캐시를 다시 사용하여 성능을 더욱 향상시킬 수 있음을 의미합니다.

Partial Rendering

형제 경로(예: 아래의 /dashboard/설정 및 /dashboard/analysis) 간을 탐색할 때 Next.js는 변경되는 경로의 레이아웃과 페이지만 가져오고 렌더링합니다. 하위 트리의 세그먼트 위에 있는 어떤 것도 다시 가져오거나 다시 렌더링하지 않습니다. 즉, 레이아웃을 공유하는 경로에서 사용자가 형제 페이지 간을 이동할 때 레이아웃이 유지됩니다.

pages

  • 페이지는 언제나 route subtree의 leaf입니다.
  • 페이지들은 기본적으로 Server Componets이지만, Client Component로 사용할 수 있습니다.
  • 페이지들은 data를 fetch할 수 있습니다.

Layouts

layout UI는 여러개의 page간 공유될 수 있습니다. 네비게이션을 할 때, layout은 상태를 보존하고, 상호작용을 유지하며, 리렌더 하지 않습니다. 또한 중첩이 가능합니다.

Good to know

  • 맨 위의 레이아웃을 루트 레이아웃이라고 합니다. 이 필수 레이아웃은 앱의 모든 페이지에서 공유됩니다. 루트 레이아웃에는 HTML 및 본문 태그가 포함되어야 합니다.
  • 어떤 route segment던지 그들만의 layout을 설정 가능합니다. 이 layout들은 해당 segment의 모든 페이지에서 공유됩니다.
  • Layout들은 기본적으로 Server Componets이지만, Client Component로 사용할 수 있습니다.
  • Layout들은 data를 fetch할 수 있습니다.
  • 부모 레이아웃과 자식 레이아웃 간에 데이터를 전달할 수 없습니다. 그러나 라우터에서 동일한 데이터를 두 번 이상 가져올 수 있으며 React는 성능에 영향을 주지 않고 요청의 중복을 자동으로 제거합니다.
  • 레이아웃이 현재 경로 세그먼트에 액세스할 수 없습니다. 경로 세그먼트에 액세스하려면 클라이언트 컴포넌트에서 Selected Layout Segment를 사용할 수 있습니다.
  • root layout은 기본적으로 Server Component이며 Client Component로 사용할 수 없습니다.

Root Layout (Required)

root layout은 app 디렉토리의 최상위에 정의되며, 모든 경로에 적용된다. 이 친구로 서버로부터 받는 초기 HTML을 수정할 수 있습니다.

Good to knwo

  • app 디렉토리는 반드시 root layout을 포함해야 합니다.
  • root layout은 반드시 htmlbody태그를 정의해야 한다. Next.js가 자동으로 만들어주지 않아요.
  • built-in SEO support를 사용해서 head HTML 요소를 관리할 수 있습니다.
  • root layout은 기본적으로 서버 컴포넌트이며, 클라이언트 컴포넌트로는 사용이 불가능하다.

Templates

Templates는 layout과 비슷하다. 여러 경로에 걸쳐 유지되고 상태를 유지하는 레이아웃과 달리 템플릿은 탐색 중인 각 자식에 대해 새 인스턴스를 만듭니다. 즉, 사용자가 템플릿을 공유하는 경로 사이를 이동할 때 컴포넌트의 새 인스턴스가 마운트되고 DOM 요소가 다시 생성되며 상태가 보존되지 않으며 다시 동기화 됩니다.

다음과 같은 특정 동작이 필요한 경우가 있을 수 있으며 레이아웃보다 템플릿이 더 적합한 선택입니다.

  • CSS 또는 애니메이션 라이브러리를 사용하여 애니메이션을 Enter/exit 합니다.
  • useEffect와 useState에 의존적인 기능이 있을때
  • 기본적인 프레임워크의 동작을 변경하기 위해서 사용합니다. 예를 들면 layouts의 Suspense Boundaries는 Layout이 최초로 로드될때만 fallback을 보여줍니다. 이후 페이지 변환때는 보여주지 않죠. 템플릿은 매번의 navigation마다 fallback을 보여줍니다.

Recommendation : 특별한 이유가 없다면 layouts를 사용하는것을 권유합니다.

공식문서를 보며 직접 눈으로 확인하고자 layout과 template을 만들고 리렌더 여부와 layout이 template을 감싸는것을 확인했습니다. 또한 레이아웃은 상태를 유지하는 반면, 템플릿은 상태를 유지하지 않는것도 확인했습니다. 완전히 다시 마운트 되나보네요!

layout과 template을 테스트

Modifying head

app 디렉토리에서는 built-in SEO 지원을 통해서 titlemeta등의 headHTML요소를 수정할 수 있습니다.

Good to knwo
루트 레이아웃에 titlemeta와 같은 head 태그를 수동으로 추가해서는 안 됩니다. 대신 스트리밍 및 <헤드> 요소 중복 제거와 같은 고급 요구 사항을 자동으로 처리하는 메타데이터 API를 사용해야 합니다.

Linking and Navigating

Next.js router는 client-side navigation과 함께 서버중심의 라우팅을 사용합니다. 즉각적인 로딩상태와 동시성 렌더링을 지원하죠. 이는 navigation이 클라이언트 측 상태를 유지하고, 값비싼 렌더링을 피하며, is interruptible, race condition에 빠지지 않는다는 것을 의미합니다.

두가지 방법의 navigate 방식이 있습니다.

  • Link 컴포넌트
  • useRouter 훅

자세히 들어가봅시다.

Link는 HTML의 a요소를 확장시켜 경로간의 prefetching과 client-side navigation을 제공하는 React component입니다. 이는 Nest.js의 경로를 navigate하는 기본 방법입니다.

import Link from 'next/link';
 
export default function Page() {
  return <Link href="/dashboard">Dashboard</Link>;
}

Linking to Dynamic Segments

template literals and interpolation을 사용해 가능하다.

링크가 active상태인지 usePathname()을 사용해서 확인 가능하다.

Scrolling to an id

Link의 기본 동작은 변경된 경로 세그먼트의 맨 위로 스크롤하는 것입니다. href에 정의된 id가 있으면 일반 a 태그와 유사하게 특정 id로 스크롤됩니다.

스크롤링을 막기 위해서는 scroll={false}를 세팅하고, href에 hashed id를 추가하고 전달합니다.

<Link href="/#hashid" scroll={false}>
  Scroll to specific id.
</Link>

The useRouter Hook

useRouter hook을 사용함으로써 클라이언트 컴포넌트 안에서 경로를 programmatically하게 변경할 수 있습니다.

Recommnendation: 특별한 이유가 없다면 Link를 사용하세요.

How Navigation Works

  • 경로 전환은 Link나 router.push()로부터 시작됩니다.
  • 경로는 브라우저의 주소창에 있는 URL을 업데이트 합니다.
  • 라우터는 클라이언트 측 캐시에서 변경되지 않은 세그먼트(예: 공유 레이아웃)를 재사용하여 불필요한 작업을 방지합니다. 이를 부분 렌더링이라고도 합니다.
  • soft navigation의 조건이 만족하면, 라우터는 서버 대신 캐시에서 새로운 세그먼트를 가져옵니다. 그렇지 않으면, hard navigation을 수행하고 서버에서 서버 컴포넌트를 불러옵니다.
  • created되었다면, payload를 fetch하는동안 로드 UI가 표시됩니다.
  • 라우터는 캐시된 페이로드 또는 새로운 페이로드를 사용해서 클라이언트의 새 세그먼트를 렌더링합니다.

Client-side Caching of Rendered Server Components

Good to know
클라이언트측 캐시는 서버측 Next.js HTTP cache와는 다릅니다.

새 라우터에는 서버 컴포넌트의 렌더링된 결과를 저장하는 in-memory client-side cache가 있습니다. 캐시는 모든 수준에서 무효화를 허용하고 동시 렌더링에서 일관성을 보장하는 route segment로 분할됩니다.

사용자가 앱을 탐색할 때 라우터는 이전에 가져온 세그먼트와 미리 가져온 세그먼트의 페이로드를 캐시에 저장합니다.

즉, 특정 경우, 라우터는 서버에 새 요청을 하는 대신 캐시를 다시 사용할 수 있습니다. 이렇게 하면 데이터를 다시 가져오고 구성 요소를 불필요하게 다시 렌더링하지 않으므로 성능이 향상됩니다.

prefetching

prefetching은 방문하기 이전에 백그라운드에서 라우트를 preload하는 방법입니다. prefetched 라우트의 그려진 결과물은 클라이언트 측 캐시에 추가됩니다. 이는 prefetched 라우트로 navigating 하는것을 near-instant에 가깝게 만들어줍니다.

기본적으로 라우트는 Link 컴포넌트를 사용해서 뷰포트에 표시될 때 prefetched됩니다.
페이지를 처음 로드하거나 스크롤할 때 발생할 수 있습니다. useRouter() 훅의 프리페치 메서드를 사용하여 프로그래밍 방식으로 루트를 프리페치할 수도 있습니다.

Static and Dynamic Routes

  • 라우트가 static이라면, 라우트 세그먼트에 대한 모든 서버 컴포넌트 payloads가 프리페치됩니다.
  • 라우트가 dynamic이면 첫 번째 공유 레이아웃에서 첫 번째 loading.js 파일까지의 페이로드가 프리페치됩니다. 이렇게 하면 전체 경로를 동적으로 미리 가져오는 비용이 줄어들고 동적 경로에 대한 상태를 즉시 로드할 수 있습니다.

Good to know

  • 프리페칭은 오직 production에서만 가능합니다.
  • 프리페칭은 Link 컴포넌트에서 비활성화 시킬 수 있습니다. prefetch={false}

Hard Navigation

Navigation 시 캐시는 무효화되고 서버는 데이터를 다시 가져와 변경된 세그먼트를 다시 렌더링합니다.

Soft Navigation

Navigation 시 변경된 세그먼트에 대한 캐시는 재사용되며(존재한다면), 서버에게 새로운 데이터 요청을 보내지 않습니다.

Conditions for Soft Navigation

탐색 시 당신이 탐색 중인 경로가 프리페치 되었으며 동적 세그먼트가 포함되지 않았거나 현재 경로와 동일한 동적 매개 변수가 있는 경우 Next.js는 Soft Navigation을 사용합니다.

예를들어, 동적인 [team] 세그먼트를 포함한 라우트를 생각해봅시다.

  • /dashboard/team-red/*에서 /dashboard/team-red/*로 이동하면 Soft Navigation이 됩니다.
    /dashboard/team-red/*에서 /dashboard/team-blue/*로 이동하면 Hard Navigation이 됩니다.

Back/Forward Navigation

아마 뒤로가기 앞으로 가기 버튼인건가? 이 동작들도 Soft Navigation으로 동작합니다. 이는 클라이언트 측 캐시가 재사용되고, navigation이 near-instant 라는것을 의미합니다.

Focus and Scroll Management

By default, Next.js will set focus and scroll into view the segment that's changed on navigation. 🤔뭐지

Routes Groups

앱 폴더의 계층은 URL 경로에 직접 매핑됩니다. 그러나 Routes Groups를 만들어 이러한 패턴을 벗어날 수 있습니다. Routes Groups는 다음과 같은 용도로 사용할 수 있습니다.

  • URL 구조에 영향을 주지 않고 경로를 구성합니다.
  • 레이아웃에 특정한 라우트 세그먼트를 삽입합니다.
  • 앱을 분할하여 여러개의 루트 레이아웃을 만듭니다.

Convention

폴더 이름을 괄호로 묶어서 Route group을 만들 수 있습니다.

Organize routes without affecting the URL path

URL에 영향을 주지 않고 라우트를 구성하고 연관있는 라우트들을 함께 유지하기 위해서 그룹을 만듭니다. 괄호안의 폴더는 URL에서 생략됩니다.

folder 구조

주소

각 그룹내에 layout.js파일을 추가해서 각 그룹에 대한 다른 레이아웃을 추가할 수 있습니다.

folder 구조

레이아웃 예제

Creating multiple root layouts

여러개의 root layouts를 만드려면, 최상위의 layout.js파일을 지우고, 각 라우트 그룹에 layout.js파일을 추가합니다. 이는 앱이 완전히 다룬 UI 또는 경험을 가진 섹션으로 분할하는데 유용합니다.

Good to know

  • 라우트 그룹 내의 경로는 동일한 URL 경로가 되지 않아야 합니다. 예를 들어, 경로 그룹은 URL 구조에 영향을 주지 않으므로 (마케팅)/about/page.js 및 (샵)/about/page.js 모두 /about으로 확인되어 오류가 발생합니다.
  • multiple root layouyts를 탐색하면 클라이언트 측 navigation과 달리 전체 페이지 로드가 발생합니다. 예를 들어 app/(shop)/layout.js를 사용하는 /cart에서 app/(marketing)/layout.js를 사용하는 /blog로 이동하면 전체 페이지가 로드됩니다. 이는 multiple root layouyts에만 적용됩니다.

Dynamic Routes

정확한 세그먼트 이름을 미리 알지 못하고 동적 데이터에서 경로를 작성하려는 경우 요청 시 입력되거나 빌드 시 미리 렌더링된 동적인 세그먼트를 사용할 수 있습니다.

Catch-all Segments

catch-all 경로는 잡아내지 못하는것을 볼 수 있습니다.

[...slug]


뒤에 파라미터가 붙으면 잡아냄!!

[[...slug]]

괄호를 하나 더 했더니 catch-all경로까지 잡아냅니다.

catch-all 뿐만아니라, 뒤에 뭔짓을 해도 다 잡아버리네요!🙌

TypeScript

export default function Page({ params }: { params: { slug: string } }) {
  return <h1>My Page</h1>;
}

Loading UI and Streaming

React 18: Suspense를 이용한 새로운 SSR 아키텍처를 읽어보면 도움이 됩니다.

Error Handling

error.js파일 컨벤션은 중첩된 라우터에서 runtime error를 우아하게 다룰 수 있도록 해줍니다.

  • 라우트 세그먼트와 중첩된 children을 React Error Boundary로 감쌉니다.
  • 앱의 다른 기능을 유지하며 영향을 받는 세그먼트에 대한 오류를 고립합니다.
  • 전체 페이지 리로드를 하지 않고 오류 복구를 시도하는 기능을 추가합니다.
'use client'; // Error components must be Client Components
 
import { useEffect } from 'react';
 
export default function Error({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  useEffect(() => {
    // Log the error to an error reporting service
    console.error(error);
  }, [error]);
 
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button
        onClick={
          // Attempt to recover by trying to re-render the segment
          () => reset()
        }
      >
        Try again
      </button>
    </div>
  );
}

How error.js Works

  • error.js는 자동으로 중첩된 child segment나 page.js를 감싸는 React Error Boundary를 만듭니다.
  • fallback error component가 활성화 되었을때, error boundary 위의 layouts는 상태를 유지하고, 상호작용이 가능합니다. 그리고 error component는 오류 복구 기능을 보여줄 수 있습니다.

Recovering From Erros

오류 컴퍼넌트는 reset() 기능을 사용하여 사용자에게 오류 복구를 시도하라는 메시지를 표시할 수 있습니다. 실행되면 Error boundary의 내용을 다시 렌더링하려고 시도합니다. 성공하면 fallback error componen가 리렌더의 결과로 대체됩니다.

Handling Errors in Layouts

error.js의 바운더리는 동일한 세그먼트간의 layout.jstemplate.js에서 던져진 error를 캐치하지 않습니다. 이는 형제간 라우트 간의 공유되는 중요한 UI를 표시하고 작동하도록 유지합니다.

특정 layout 또는 template에서 오류를 핸들링하기 위해서는 error.js파일을 layouts의 부모 세그먼트에 위치시켜야 합니다.

루트 layout 또는 루트 template 오류를 핸들링하기 위해서 global-error.js라 불리는 error.js의 변형을 사용해야 합니다.

Handling Errors in Root Layouts

루트의 app/error.js는 루트의 app/layout.js 또는 app/template.js컴포넌트의 에러를 잡아내지 않습니다. 이를 app/global-error.js에서 제어합니다.

Parallel Routes

병렬 라우팅을 사용하면 동일한 레이아웃에서 하나 이상의 페이지를 동시에 또는 조건부로 렌더링할 수 있습니다. 이들은 개별적으로 스트리밍되는 각 경로에 대해 독립적인 에러와 로딩 스테이트를 가집니다.

Convention

병렬 라우트는 @folder 컨벤션을 따릅니다. 이들은 URL구조에 영향을 주지 않습니다.

내가 뭘 놓친건지 이유는 아직 모르겠지만... root layout에서 이를 사용하려고 하니까 404 페이지를 보여주었다.

root

@test

이를 Routes Groups에서 사용해봤다.

@test

역시 안된다. @test 폴더를 malza 폴더 안으로 넣어보자.
@test

@test

"use client";
import { useSelectedLayoutSegment } from "next/navigation";
import { useState } from "react";

export default function Layout(props) {
  const [count, setCount] = useState(0);
  const segment = useSelectedLayoutSegment();

  return (
    <div style={{ border: "2px solid blue", height: "100%", padding: "20px" }}>
      <h1>Active segment: {segment}</h1>

      <button onClick={() => setCount((cur) => cur + 1)}>1뎁스 업</button>
      <br />
      <button onClick={() => setCount((cur) => cur - 1)}>1뎁스 다운</button>
      <br />
      <p>여긴 1depth 레이아웃입니다.</p>
      <b>변경된 1뎁스 카운터는 : {count}</b>
      {props.children}
      {props.test} // 이놈이 그려진거임!
    </div>
  );
}

test page가 떴다! root에서 안되고, route group의 최상위 layout에서도 안되는데.. 원인은 솔직히 더 읽어봐야겠음!

Routing 파트를 다 적고 싶었는데... 끝날 기미가 안보인다 ㅎㅎ... 파트를 나눠야겠어.

profile
그냥 개인적으로 공부한 글들에 불과

0개의 댓글