Back-end/Next.js

[Next.js] 2. Routing에 대하여

류건 2024. 5. 19. 21:09
반응형

 

이전에 next.js 프로젝트 생성 후 구조를 분석하는 글을 포스팅했다. 이번에는 Routing에 대해서 공부해보도록 하겠다.

 

[Next.js] 1. Next.js 시작하기

내가 몸 담고 있는 국민대학교 소프트웨어융합대학 웹 학술 동아리 대WINK의 공식 홈페이지 만들기에 사용된 Next.js spring이랑 express.js만 해봤기 때문에 typescript나 react에 대한 선수 지식이 없어서

sksmsfbrjs51.tistory.com

 

1. Routing 구조

- Nested Routing (중첩 경로)

원하는 엔드포인트를 설정하기 위해 directory/directory 구조로 중첩하여 생성하는 것이다.

 

중첩 경로에서는 세그먼트의 구성 요소 가 상위 세그먼트의 구성 요소 내에 중첩된다.

- Dynamic Routing(동적 라우팅)

사진처럼 Path Variable에 따라 동적으로 페이지를 렌더링하는 방식이다.

 

2. 레이아웃 & 템플릿

특수 파일인 layout.js  template.js를 사용하면 경로 간에 공유되는 UI를 생성할 수 있다.

 

- layout

레이아웃은 여러 경로 간에 공유되는 UI이다. 탐색 시 레이아웃은 상태를 유지하고 대화형을 유지하며 다시 렌더링되지 않는 특징을 지닌다.

 

기본적으로 layout.js을 통해 React component를 export할 수 있다. 이는 하위 page를 wrapping하는 효과를 가진다.

또한 해당 component는 children을 통해서 하위 페이지들을 보여준다.

 

예시로, 아래 사진처럼 dashboard/layout.js 는 /dashboard 페이지와 /dashboard/settings 페이지 모두에게 적용된다. 

 

- root layout

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        {/* Layout UI */}
        <main>{children}</main>
      </body>
    </html>
  )
}

 

app 디렉터리의 최상위 수준에서 정의되며 모든 경로에 적용된다.

이 레이아웃은 필수이며 서버에서 리턴된 초기 HTML을 수정할 수 있도록 html과 body 태그 포함해야 한다.

 

webstorm에서 작업 시 해당 layout.tsx 파일이 없으면 자동으로 생성되는 현상을 발견했다. 필수라는 뜻이겠지.

 

- Nested Layout

기본적으로 폴더 계층 구조의 레이아웃은 중첩되어 있다.  즉, children props를 통해 하위 레이아웃 또는 페이지를 wrapping 한다는 말이다.

 

밑의 사진처럼 layout.js를 특정 경로의 세그먼트 내부에 추가하여 레이아웃을 중첩할 수 있다 .

 

 

- app/dashboard/layout.js

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return <section>{children}</section>
}

 

 

실행 결과

- Template

기본적으로 Layout은 페이지를 이동해도 re-rendering이 발생하지 않는다. Layout에게도 mount 되어야 하는 상황이 발생하게 되면 사용하는 것이 바로 Template이다.

 

Template은 레이아웃과 유사하게 자식 레이아웃이나 페이지를 감싸는 역할을 한다. Layout과 다른 점은 탐색할 때, 자식 요소마다 새로운 인스턴스를 생성한다. 같은 템플릿을 사용하는 router로 이동할 때도 새로운 인스턴스가 마운트되고, DOM 요소가 다시 생성되며 상태가 보존되지 않는다.

 

따라서 

  • useEffect 탐색 시 재동기화.
  • 하위 클라이언트 component의 상태 재설정.
  • 페이지를 넘나들 때마다 무언가를 기록해야 하는 상황.

등과 같은 상황에서 사용된다.

 

공식 문서에 따르면, layout을 사용하는게 더 좋다고 명시되어 있음.

 

 

- app/template.tsx

export default function Template({ children }: { children: React.ReactNode }) {
  return <div>{children}</div>
}

 

 

- Template 렌더링 위치

중첩 측면에서 template.js 레이아웃과 해당 하위 항목 사이에 렌더링된다.

<Layout>
  {/* Note that the template is given a unique key. */}
  <Template key={routeParam}>{children}</Template>
</Layout>

 

- metadata

metadata API 를 사용하여 title, meta와 같은 <head> HTML 요소를 수정할 수 있다.

 

app/page.tsx

import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: 'Next.js',
}
 
export default function Page() {
  return '...'
}

 

- usePathname()

현재 url을 알 수 있는 Client Component hook이다. 사용 시 'use client'를 꼭 써줘야 한다.

 

ex> app/ui/nav-links.tsx

'use client'

import { usePathname } from 'next/navigation'
import Link from 'next/link'

export function NavLinks() {
    const pathname = usePathname()

    return (
        <nav>
            <Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
                Home
            </Link>
            <br/>
            <Link className={`link ${pathname === '/about' ? 'active' : ''}`} href="/about">
                About
            </Link>
        </nav>
    )
}

 

app/layout.tsx

import { NavLinks } from '@/app/ui/nav-links'
 
export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <NavLinks />
        <main>{children}</main>
      </body>
    </html>
  )
}

 

3. Linking & Navigating

Next.js에서 경로 탐색 방법은 다음과 같다.

  • <Link> component 사용
  • useRouter hook 사용 (Client Component)
  • redirect 기능 사용 (Server Component)
  • Navigate History API 사용

 

- <Link> Component

<Link>는 HTML <a> 태그를 확장하여 경로 간 프리패치 및 클라이언트 측 탐색을 제공하는 내장 구성 요소이다.

 

app/page.tsx

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

 

- 동적 세그먼트 연결

ex> 블로그 목록을 props로 받아 동적으로 블로그 게시글 목록 생성하기.

import Link from 'next/link'
 
export default function PostList({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <Link href={`/blog/${post.slug}`}>{post.title}</Link>
        </li>
      ))}
    </ul>
  )
}

 

- ID로 스크롤

  • Next.js 앱 라우터의 기본 동작은 새 경로의 맨 위로 스크롤하거나 앞뒤 탐색을 위해 스크롤 위치를 유지하는 것.
  • 탐색 시 특정 ID로 스크롤하려면 # 해시 링크를 URL에 추가하거나 해시 링크를 href 속성에 전달
  • 이는 <Link> 컴포넌트가 <a> 태그로 렌더링되기 때문에 가능하다!
<Link href="/dashboard#settings">Settings</Link>
 
// Output
<a href="/dashboard#settings">Settings</a>

스크롤 복원 비활성화

위에서 얘기했듯, next.js는 기본적으로 앞뒤 탐색을 위해 스크롤 위치를 유지할 수 있는데, 이 동작을 비활성화하려면 <Link> 컴포넌트에 scroll={false}를 전달해주거나 router.push 또는 router.replace()에 scroll: false를 전달한다.

// next/link
<Link href="/dashboard" scroll={false}>
  Dashboard
</Link>

// useRouter
import { useRouter } from 'next/navigation'
 
const router = useRouter()
 
router.push('/dashboard', { scroll: false })

 

 

useRouter() Hook

  • useRouter() hook을 사용하면 프로그래밍 방식으로 경로를 변경할 수 있다.
  • 이 hook은 client-component 내에서만 사용할 수 있으며 next/navigation에서 가져올 수 있다. (client-side에서만 실행되기 때문!)
'use client'
 
import { useRouter } from 'next/navigation'
 
export default function Page() {
  const router = useRouter()
 
  return (
    <button type="button" onClick={() => router.push('/dashboard')}>
      Dashboard
    </button>
  )
}

 

useRouter를 사용해야 하는 특별한 요구 사항이 없을 경우 <Link> 컴포넌트를 사용하는 것을 권장한다.

 

 

- redirect

server component에서는, redirect 함수를 사용한다.

import { redirect } from 'next/navigation'
 
async function fetchTeam(id: string) {
  const res = await fetch('https://...')
  if (!res.ok) return undefined
  return res.json()
}
 
export default async function Profile({ params }: { params: { id: string } }) {
  const team = await fetchTeam(params.id)
  if (!team) {
    redirect('/login')
  }
 
  // ...
}

 

4. Routing 및 Navigation 작동 방식

  • App Router는 라우팅 및 탐색을 위해 하이브리드 접근 방식을 사용한다. 서버에서 애플리케이션 코드는 경로 세그먼트 별로 자동으로 코드 분할된다.
  • 클라이언트에서 Next.js는 경로 세그먼트를 미리 가져오고 캐시함. 즉 사용자가 새 경로로 이동할 때 브라우저는 페이지를 다시 로드하지 않고 변경된 경로 세그먼트만 다시 렌더링하여 탐색 경험과 성능을 향상시킨다.

1. Prefetching (프리패칭)

  • 프리 패칭은 사용자가 경로를 방문하기 전에 백그라운드에서 경로를 미리 로드하는 방법. Next.js에서 경로를 미리 가져오는 방법에는 두 가지가 있습니다.
    • <Link> 컴포넌트 : 경로가 사용자의 뷰포트에 표시되면 자동으로 미리 가져온다. 프리 패칭는 페이지가 처음 로드될 때 또는 스크롤을 통해 표시될 때 발생
    • router.prefetch() : useRouter hook을 사용하여 프로그래밍 방식으로 경로를 미리 가져올 수 있다.
  • <Link>의 프리패치 동작은 정적 경로와 동적 경로에 따라 다름.
    • 정적 경로 : prefetch 기본값은 true. 전체 경로가 프리패치되고 캐시된다.
    • 동적 경로 : prefetch 기본값은 auto. 첫 번째 loading.js 파일이 프리패치되고 30초동안 캐시될 때까지 공유 레이아웃만 표시됨.
    • 이렇게 하면 전체 동적 경로를 가져오는 비용이 줄어들고 사용자에게 더 나은 시각적 피드백을 제공하기 위해 즉시 로드 상태를 표시할 수 있다.
  • prefetch prop을 false로 설정하여 프리패칭을 비활성화 할 수 있다.
  • 프리페칭은 개발 중에는 활성화되지 않고 프로덕션에서만 활성화된다.

2. Caching (캐싱)

  • Next.js에는 라우터 캐시라는 메모리 내 클라이언트 측 캐시가 있다.
  • 사용자가 앱을 탐색할 때 미리 가져온 경로 세그먼트와 방문한 경로의 React Server 컴포넌트 페이로드가 캐시에 저장됨.
  • 이는 탐색 시 서버에 새로운 요청을 하는 대신 캐시를 최대한 재사용하여 요청 수와 전송되는 데이터 수를 줄여 성능을 향상한다는 의미.

3. Partial Rendering (부분 렌더링)

  • 클라이언트에서 탐색을 다시 렌더링할 때 변경되는 경로 세그먼트만 의미하며 모든 공유 세그먼트는 보존됨.
  • 예를 들어 두 형제 경로 및 사이를 탐색하는 경우 /dashboard/settings 에서 /dashboard/analytics 로 이동하는 경우 analytics page만 렌더링되고 dashboard의 layout.js은 유지.

4. Soft Navigation (소프트 네비게이션)

  • 기본적으로 브라우저는 페이지 간 하드 탐색을 수행한다. useState는 브라우저가 페이지를 다시 로드하고 앱의 hook과 같은 React 상태와 사용자의 스크롤 위치 또는 초점이 맞춰진 요소와 같은 브라우저 상태로 재설정한다는 것을 의미.
  • 그러나 Next.js에서 앱 라우터는 소프트 탐색을 사용한다. 즉, React가 React와 브라우저 상태를 유지하면서 변경된 세그먼트만 렌더링하며 전체 페이지를 다시 로드하지 않는다.

5. Back and Forward Navigation (뒤로 또는 앞으로 탐색)

  • 기본적으로 Next.js는 앞뒤 탐색을 위한 스크롤 위치를 유지하고 라우터 캐시의 경로 세그먼트를 재사용한다.

참고

 

Next.js 14 시작하기 #2 Nested, Dynamic Routing

Next.js 14 시작하기 Catch-all Segment Nested, Dynamic Routing

velog.io

 

Building Your Application: Routing | Next.js

Learn the fundamentals of routing for front-end applications.

nextjs.org

 

반응형