본문 바로가기

개발/React.js

Dynamic Route - Slug

목적

정확한 세그먼트 이름을 미리 알지 못하고 동적 데이터에서 경로를 생성하는 경우 요청 시 채워지거나 빌드 시 사전 렌더링되는 동적 세그먼트를 사용할 수 있다.

Next.js의 Slug를 적극 활용해야 하는 이유

App routing의 Slug 기능을 사용하여 동적 라우팅(Dynamic Routing)을 구현하려고 한다. 이 기능은 일반적으로 다음과 같이 사용된다.

// app/blog/[slug]/page.tsx

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

이렇게 path param(segment)으로 온 param 데이터를 사용할 수 있는 것이 장점이다.

그런데, 우리가 의도하고자 하는 형식은 사실 더 depth가 깊다.

app/[company]/[insurance]/[userEmail]/page.tsx

복잡하게 생각할 것 없이 위 방법대로… 있는대로(?) 구현하면 다음과 같이 잘 나온다.

 
export default function page({
    params,
}: {
    params: { company: string; insurance: string; userEmail: string };
}) {
    return (
        <div className="flex flex-col items-center">
            <h1 className="text-xl text-center">사용자 맞춤 청구 페이지</h1>
            <div>
                <ul>
                    <li>
                        <strong>company: {params.company} </strong>
                        <strong>insurance: {params.insurance} </strong>
                        <strong>userEmail: {params.userEmail} </strong>
                    </li>
                </ul>
            </div>
        </div>
    );
}
 

음… 직관적이긴 한데 좀 지저분한 감이 없지 않아 있다. 더 나은 방법은 없을까?


GenerateStaticParams 함수

위의 방식대로 미리 segment를 지정하는 것도 좋지만, API를 통해 데이터를 받아와서 동적 라우팅을 구성하기 위한 방법은

공식 문서에 의하면 generateStaticParams를 다음과 같이 쓰라고 한다.

https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes

export async function generateStaticParams() {
  const posts = await fetch('https://.../posts').then((res) => res.json())
 
  return posts.map((post) => ({
    slug: post.slug,
  }))
}

이 기능의 주요 이점은 generateStaticParams스마트한 데이터 검색입니다. generateStaticParams요청을 사용하여 함수 내에서 콘텐츠를 가져오는 경우 fetch요청이 자동으로 메모 됩니다 . 이는 fetch여러 , 레이아웃 및 페이지에서 동일한 인수를 사용하는 요청이 generateStaticParams한 번만 이루어지므로 빌드 시간이 단축됨을 의미합니다.

export async function generateStaticParams() {
    const usersData: Promise<User[]> = getAllUsers()
    const users = await usersData

    return users.map(user => ({
        userId: user.id.toString()
    }))
}

사실 테스트는 아직 못 해보긴 했는데, 추후 api를 활용하여 dir를 구성해야 한다면 위의 방법을 활용해야 할 것 같다.

위처럼 depth를 깊게 들어가서 params를 받는 방법도 있지만, 좀 더 젠틀하고 나이스한 방법으로(?) Multi Segment를 사용하기 위한 방법과 연관된 것이 아마 아래의 케이스들인 것 같다.


포괄 세그먼트(Catch-all Segments)

 
export default function page({ params }: { params: { slug: string[] } }) {
    return (
        <div className="flex flex-col items-center">
           //...
                <li>
                    <strong>company: {params.slug[0]} </strong>
                    <strong>insurance: {params.slug[1]} </strong>
                    <strong>userEmail: {params.slug[2]} </strong>
                </li>
            //...
        </div>
    );
}
 
 

동일한 경로에 동적 라우팅 segment가 들어가있으면 next.js가 헷깔려해서 test로 depth를 하나 더 깊게 들어가서 테스트했다. 그런데 문제는, params.slug[0] 이런식으로 인덱스 순서에 따라 params를 가져와야 하는 것이다. 이게 최선인가 싶어 블로그를 뒤져봤는데, builder.io의 어떤 작자도 이렇게 쓰고 있더라. https://www.builder.io/blog/next-14-app-router

export default function Doc({ params }: { params: { slug: string[] } }) {
  if (params.slug.length === 2) {
    return (
      <h1>
        Viewing docs for feature {params.slug[0]} and concept {params.slug[1]}
      </h1>
    );
  } else if (params.slug.length === 1) {
    return <h1>Viewing docs for feature {params.slug[0]}</h1>;
  }
  return <h1>Docs home page</h1>;
}

다른 사람도 이렇게 쓰고 있다. https://velog.io/@jay/Next.js-13-master-course-routing-3 (매우 잘 설명해뒀음…)

const ShopPage = ({ params }: ShopPageProps) => {
  const category = params.slug?.[0] ?? null
  const itemId = params.slug?.[1] ?? 0

명시적으로 param을 표현하는 방법은 첫 번째 방법으로 depth가 깊더라도 명시적인 동적 라우팅을 쓰는 방법이 유일하지 않을까 예상한다. 물론, 굳이 명시적일 필요가 없다면 프로젝트 내 디렉토리를 깔끔하게 구성하기 위해 catch-all segments를 적극 활용하는 것이 좋겠다.


선택적 포괄 세그먼트(Optional Catch-all Segments)

이중 대괄호 안에 매개변수를 포함하여 포괄 세그먼트를 선택사항으로[[...folderName]] 만들 수 있습니다 .

예를 들어 , , 외에도 , 일치 app/shop/[[...slug]]/page.js합니다 ./shop/shop/clothes/shop/clothes/tops/shop/clothes/tops/t-shirts

catch-all 세그먼트 와 선택적 catch-all 세그먼트 의 차이점은 선택 사항을 사용하면 매개변수가 없는 경로도 일치한다는 것입니다( /shop위 예에서).

 

포괄적인 세그먼트는 [[...folderName]]과 같이 이중 대괄호 안에 매개 변수를 포함하여 옵션으로 만들 수 있습니다. 예를 들어 app/shop/[[...slug]]/page.js는 /shop/clothes, /shop/clothes/tops, /shop/clothes/tops/t-shirts 외에도 /shop과도 일치합니다.

catch-all 과 선택적 catch-all 세그먼트의 차이점은 선택적 catch-all 세그먼트의 경우 파라미터가 없는 라우터도 매치 된다는 점이다

위의 두 방법대로 하게 되면, 복잡하게 디렉토리 깊이를 깊게 가져가지 않아도 되니 훨씬 좋은 방법인 것 같다.

app/[company]/[insurance]/[userEmail]/page.tsx
app/[...slug]/page.tsx
app/[[...slug]]/page.tsx

그림으로 나타내면 다음과 같다.

 

차이점

그렇다면, 그냥 [slug]로 사용되는 케이스와 포괄 세그먼트, 선택적 포괄 세그먼트는 무슨 차이가 있을까?

각각의 차이점을 알아보자.

Dynamic Routes특징

Standard

  • 동적으로 생성은 가능하나, 계층까지 확장되진 않는다.

Catch-all Segments

  • 계층 확장
  • 항상 사용자 정보를 알고 있다는 전제
 
 
카테고리가 존재하지 않을 때 유용하게 사용할 수 있다.

Optional Catch-all Segments

  • 계층 확장
  • 항상 사용자 정보를 알고 있지 않다는 전제 → /shop으로 접근시 404 방지
    • null일 경우 이 컴포넌트를 보여줘라.

참고

https://hjk329.github.io/next.js/Next.js%EC%9D%98-Routing/

https://blog.greenroots.info/dynamic-routes-nextjs-app-router

https://www.builder.io/blog/next-14-app-router

https://velog.io/@jay/Next.js-13-master-course-routing-3

https://rocketengine.tistory.com/entry/NextJS-13-Routing-Dynamic-Routes


라우팅 적용 방안

Case 1.

  • 링크 A(root) - src/page.tsx
  • 링크 B - src/main/page.tsx
  • 청구 페이지 - src/app/[company]/[insurance]/[userEmail]/page.tsx
  • 완료 - src/complete/page.tsx

Case 2.

  • 링크 A(root) - src/page.tsx
  • 링크 B - src/main/page.tsx
  • 청구 페이지 - src/app/[...users]/page.tsx
  • 완료 - src/complete/page.tsx

Case 3.

  • 링크 A(root) - src/page.tsx
  • 링크 B - src/main/page.tsx
  • 청구 페이지 - src/app/[[...users]]/page.tsx
  • 완료 - src/complete/page.tsx

QueryString 활용

아름답게 위의 방법대로 하려고 했으나, s3에 정적 배포를 하려고 하니 index.html의 부재가 생겼고,

next.config에서 output: “export” 설정에 꼭 해주어야 s3에 배포가 되지만, 앱 라우팅은 SSR이라 Build Fail이 된다.

그래서 다른 방법이 없을까 고민해보니 Query String을 브라우저의 Url에서 따오는 방법도 고려해보았다.

 
export default function Claim() {
    useEffect(() => {
        const userUrl = window.location.search;
        console.log(`[청구 페이지] 현재 url queryString? : ${userUrl}`);
        const params = window.location.search.split("&");
        console.log("[청구 페이지] params >>>>>> ", params);
    }, []);
    // ...
}

상기 코드를 사용하여 해결은 되었으나 살짝 불안정한 감은 있었다.


Vercel 배포

 

 

 

 

'개발 > React.js' 카테고리의 다른 글

React 주민등록번호 컴포넌트  (0) 2024.09.13
Xlsx parsing + react-table  (0) 2024.09.13
OAuth 인증 - 인가에 대하여  (0) 2024.09.13
전역 상태 관리 라이브러리 - Zustand vs Recoil  (0) 2024.09.13
파일 Drag & Drop  (0) 2024.09.13