Next 13 - Routing

이노아·2023년 11월 23일
0

Next.js 13

목록 보기
2/4
post-thumbnail

Next.js 13 Routing

이번 차라리 내가 정리에서는 Next.js 13의 routing에 대해 정리하고자 합니다.

next.js 13의 routing은 폴더와 파일 구조를 이용한 routing이 가능한 file system 기반의 file-based routing을 제공합니다.

next.js 13의 routing의 기본은 건너띄고 routing의 세부사항에 대해 알아보도록 하겠습니다.


동적/중첩 라우팅(Dynamic Routing)

동적 라우팅

사용자가 상품 상세 페이지에서 상품을 확인하는 예를 통해 동적 라우팅에 대해 정리해보도록 하겠습니다.

위의 경우는 크게 두가지로 구분할 수 있을 것입니다.

  • 데이터베이스에 해당 상품에 대한 데이터가 있을 경우
  • 데이터베이스에서 해당 상품에 대한 데이터가 제거되었을 경우

데이터베이스에 해당 상품에 대한 데이터가 있을 경우 사용자는 상품에 대한 상세 정보를 확인할 수 있을 것이고, 데이터가 제거되었을 경우 사용자는 상품에 대한 상세 정보를 페이지에서 확인할 수 없을 것입니다.

이 때 next.js 13의 동적 라우팅을 적용할 수 있습니다.

위의 next.js 문서의 동적 라우팅 이미지의 경우 기본적으로 /blog url을 사용하고 /a, /b, /c의 url에서 blog의 id를 전달 받아 페이지를 동적으로 구성합니다.

blog세그먼트의 하위 세그먼트인 [slug]에서 blog의 id를 동적으로 전달받아 page.tsx에 전달하게 됩니다. 저희는 동적인 값 id를 이용해 상품을 상세 정보를 가져와 페이지를 구성할 수 있게 되는 것입니다.

위의 내용을 이용해 우선 데이터베이스에 해당 상품에 대한 데이터가 있을 경우에 대해 살펴보겠습니다.

productInfo세그먼트에 하위 세그먼트로 [productId]세그먼트를 만들고 page.tsx를 만들어 줍니다.

/productInfo/[productId]/page.tsx

type PageParams = {
  productId: string
}

const ProductInfoPage = ({ params }: { params: PageParams }) => {
  console.log(params.productId)

  return (
    <div className="h-[500px]">
      <h1>상품 상세 페이지</h1>

      <span>{params.productId}</span>
    </div>
  )
}

export default ProductInfoPage

[productId] 세그먼트의 page.tsx에서 props로 동적 세그먼트에 접근이 가능해지게 됩니다.

중첩 동적 라우팅

동적 세그먼트를 중첩해서 사용하는 방법은 동적 세그먼트의 하위 세그먼트로 동적 세그먼트를 추가해주면 됩니다.

상품의 id와, 사용자 email을 전달받는 예를 통해 살펴보도록 하겠습니다.

/productInfo/[productId]/[email]/page.tsx

type PageParams = {
  productId: string
  email: string
}

const ProductInfoPage = ({ params }: { params: PageParams }) => {
  console.log({ params })

  return (
    <div className="h-[500px]">
      <h1>상품 상세 페이지</h1>

      <p>{params.productId}</p>
      <p>{params.email}</p>
    </div>
  )
}

export default ProductInfoPage

이제[productId], [email] 2개의 동적 세그먼트에 접근이 가능해집니다.

🚨 참고하실 점은 url path /productInfo/[productId]/productInfo/[productId]/[email]는 params의 값이 다르다는 것입니다.

url path가 /productInfo/[productId] 일 경우 params는 productId의 값만 가지며, /productInfo/[productId]/[email] productId, email의 값을 가지게 됩니다.

이는 각 url path의 page.tsx에 접근하는 것이 layout.tsx와는 다르기 때문입니다.

layout.tsx의 경우 거치는 세그먼트의 레이아웃이 중첩되어 적용되었지만, page.tsx의 경우 마지막 세그먼트의 page.tsx만 적용됩니다.





프로젝트 적용

프로젝트 적용 파트에서는 사용자가 상품을 클릭하면 상품 상세 페이지로 이동하는 것에 대해 정리하고자 합니다.

app/productInfo/[productId]/page.tsx

type PageParams = {
  productId: string
  email: string
}

const ProductInfoPage = ({ params }: { params: PageParams }) => {
  // 선택한 상품 상세 정보 가오져오는 Query
  const { productInfo } = useGetProductInfoQuery(params.productId)

  console.log(productInfo)

  return (
    <div className="h-[500px]">
      <h1>상품 상세 페이지</h1>

      <p>{params.productId}</p>
    </div>
  )
}

export default ProductInfoPage요

사용자가 상품을 클릭하게 되면 앞서 정리한 동적 라우팅을 이용하여 [productId] 세그먼트에 들어오는 동적 세그먼트에 페이지의 prams로 접근하여 선택한 상품의 상세 데이터를 가져오는 api에 상품의 id를 전달해주었습니다.

productInfo를 consol.log 출력해 보니 정상적으로 상품 상세 데이터를 가져온 것을 확인할 수 있습니다.

이렇게 가져온 상품 상세 데이터를 이용해 상품 상세 페이지를 구성할 수 있게 됩니다.






catch all segments

동적 세그먼트는 괄호 안에 ...를 prefix로 붚여 ...폴더이름 안에 모든 후속 세그먼트를 포함하도록 확장이 가능합니다.

위의 정의만 봤을 때는 아직은 모호합니다. 간단한 예시를 통해 살펴보도록 하겠습니다.

  • url path가 /shop/[slug]
  • url path가 /shop/[...slug]

위의 두 url path에는 어떤 차이가 있을까요?

첫번째 url path /shop/[slug]의 경우 동적 세그먼트인 [slug]의 slug에 접근하여 /shop/1, /shop/2, shop/3 등으로 확장이 가능합니다. 즉, 해당 계층까지만 확장이 가능한 것입니다.

두번째 url path /shop/[...slug]의 경우 괄호 안에 ...을 이용하고 있습니다. 이를 통해 /shop/a, /shop/b, /shop/c는 물론이고, /shop/a/1, /shop/b/2 등으로 설정한 세그먼트보다 깊은 계층의 url path까지도 확장이 가능해집니다.



프로젝트에서는 shop 세그먼트의 하위 세그먼트인 [slug] 세그먼트의 계층이 얼마나 확장될지 예상할 수 없을 경우에 유용하게 사용할 수 있습니다.

사용자가 카테고리를 선택하는 경우 세그먼트의 계층이 얼마나 깊어질지 예상할 수 없을 것입니다.
어떤 경우는 url path가 /shop/clothes에서 끝날 수도 있을 것이고, 어떤 경우는 한 단계 확장된 /shop/clothes/tops일 수도 있을 것입니다.

세그먼트 접근

페이지에서는 세그먼트에 어떻게 접근할 수 있을까요?

app/categoryProductManagement/[...category]/page.tsx

interface ICategoryProductManagementPageProps {
  params: {
    category: string[]
  }
}

const CategoryProductManagementPage = ({
  params,
}: ICategoryProductManagementPageProps) => {
  console.log(params)

  return <div>카테고리별 상품 페이지</div>
}

catch all segments의 경우 페이지의 prams에서 접근할 경우 배열로 각 세그먼트들을 받을 수 있습니다.

  • url path가 /categoryProductManagement/tops 인 경우 ➡️ { category: [ 'tops' ] }
  • url path가 /categoryProductManagement/tops/outer 인 경우 ➡️ { category: [ 'tops', 'outer' ] }

위의 예시처럼 세그먼트의 계층만큼 배열에 추가됩니다.






Optional Catch-all Segments

지금까지 catch all segments를 이용해 상품 카테고리 기능을 구현할 수 있는 틀을 마련했지만, 문제가 있습니다.

/categoryProductManagement/tops, /categoryProductManagement/tops/outer 등 categoryProductManagement 세그먼트의 하위 세그먼트 계층을 catch all 했지만,

categoryProductManagement 페이지에서는 모든 카테고리 별 상품에 대해 확인할 수 있어야 되는데, 정작 url path /categoryProductManagement 로 접근할 경우 404페이지가 나옵니다.

이때 대안으로 사용할 수 있는 것이 Optional Catch-all Segments 입니다.

사용법은 catch all segments에 대괄호를 한번더 감싸준 [[...slug]] 세그먼트를 사용하는 것입니다.

위의 이미지에서 url path가 /shop일 경우 {}인 것을 확인할 수 있습니다.

이를 통해 /categoryProductManagement로 접근을 해도 별도의 페이지를 작업하지 않고 동일 세그먼트 배열에서 페이지 처리가 가능해집니다.

app/categoryProductManagement/[[...category]]/page.tsx

interface ICategoryProductManagementPageProps {
  params: {
    category: string[]
  }
}

const CategoryProductManagementPage = ({
  params,
}: ICategoryProductManagementPageProps) => {
  const category = params.category?.[0] ?? null

  return !category ? (
    <div>전체 카테고리 페이지</div>
  ) : (
    <div>카테고리별 상품 페이지</div>
  )
}

url path가 /categoryProductManagement일 경우 category는 null이기 때문에 null일 경우 전체 카테고리를 관리하는 컴포넌트를, /categoryProductManagement보다 깊은 계층의 url path로 접근할 경우 카테고리별 상품 컴포넌트를 렌더링하게 되면 별도의 페이지 작업없이 페이지 처리를 할 수 있게 됩니다.

프로젝트 적용

코드를 입력하세요




지금까지 Next.js 13의 라우팅을 프로젝트에 적용한 예시 코드를 통해 정리해보았습니다. 감사합니다!

profile
어제의 Best Practice를 오늘의 Legacy로

0개의 댓글