4. loading, Suspense, error boundary, zod, dynamic routes, searchParams 사용 예시

Dynamic Routes

Dynamic Routes는 관행적으로 [slug]라는 이름으로 많은 상품명에 따라 동일한 레이아웃을 가져갈 때 사용됩니다.

반드시 slug라는 이름을 사용해야하는 것은 아니며, 가독성을 위해 아래는 categoryname이라는 이름으로 예시를 작성하였습니다.

 

다음 폴더 구조에서의 예시

└── category
     └── [categoryname]
          └── page.tsx

 

page.tsx

import React from "react";

const Page = ({ params }: { params: { categoryname: string } }) => {
  return (
    <div>
      <h1>{params.categoryname}</h1>
    </div>
  );
};

export default Page;

 

브라우저에 http://localhost:3000/category/dress 를 입력하면

dress가 렌더링됩니다.

React에서 react-router-dom을 사용한다면 수많은 Nested Routing을 처리하는 것만으로 코드가 길어지지만 Next.js에서는 위와 같은 params를 직접 받을 수 있습니다.

 

searchParams

searchParams는 상품 검색시 uri로 검색 정보를 넘길 때 사용됩니다.

page.tsx

const Page = ({
  searchParams,
}: {
  searchParams: { id: string | undefined };
}) => {
  return (
    <div>
      <h1>{searchParams.id}</h1>
    </div>
  );
};

export default Page;

 

client component에서도 동일한 코드를 구현할 수 있다.

"use client";

import { useSearchParams } from "next/navigation";

const Page = () => {
  const searchParams = useSearchParams();
  const id = searchParams.get("id");
  return (
    <div>
      <h1>{id}</h1>
    </div>
  );
};

export default Page;

 

 

loading, error boundary, zod, dynamic routes, searchParams 사용 예시

폴더 구조

├── events
    ├── [id]
    │   ├── loading.tsx
    │   └── page.tsx
    └── error.tsx

 

zod 설치

pnpm add zod

 

설명은 주석으로 대체하였습니다.

import { Suspense } from 'react';
import Loading from './loading';
import { z } from 'zod';

interface Props {
  params: { id: string };
  searchParams: { [key: string]: string | string[] | undefined };
}

//  http://localhost:8077/events/seoul?sp1=test&pageno=2  접속시
// params = { id: 'seoul' }
// searchParams = { sp1: 'test', pageno: '2' }

const pageNumberSchema = z.coerce.number().int().positive();
// searchParam value가 string이므로 숫자로 바꿔주고, 정수이면서 양수인지를 체크한다.

const Page = ({ params, searchParams }: Props) => {
  console.log('search', searchParams);

  const parsedPageNo = pageNumberSchema.safeParse(searchParams.pageno);

  console.log('parsedPageNo', parsedPageNo);
  // pageno값이 '2'이면 결과 : parsedPageNo { success: true, data: 2 }
  // pageno값이 '0'이면 결과 : parsedPageNo { success: false, error: [Getter] }
  if (!parsedPageNo.success) {  
  	// 즉, 양수가 아니면 에러를 던진다.
    throw new Error('Invalid page number');
  }
  // error.tsx는 같은 폴더에 있어도 되지만 없다면 상위 폴더의 error.tsx가 실행된다.

  return (
    <main className="py-24 text-center">
      <Suspense key={parsedPageNo.data} fallback={<Loading />}>
        <p>page number is {parsedPageNo.data}</p>
      </Suspense>
    </main>
  );
};

export default Page;

 

loading.tsx에는 spinner 애니메이션이나 아이콘을 등록할 수도 있고,

UI library를 이용하여 <Skeleton />을 등록할 수도 있습니다.

 

error.tsx는 반드시 client component에서만 동작합니다.

error.tsx

'use client'; // Error components must be Client Components

import { useEffect } from 'react';

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    // Log the error to an error reporting service
    console.error(error);
  }, [error]);

  return (
    <main className="py-24 text-center">
      <p>{error.message}</p>
      <button
        className="mt-4 border bg-blue-500 px-4 py-2 text-white"
        onClick={
          // Attempt to recover by trying to re-render the segment
          reset
        }
      >
        Try again
      </button>
    </main>
  );
}

 

 

만약 error.tsx가 없다면 다음과 같이 web app 전체가 crash되는 현상이 발생합니다.

 

위와 같이 error boundary를 통해 

error를 처리하여 전체 app crash를 방지하고, reset 코드를 부여해줄 수 있습니다.

위의 경우 searchParams로 유효성 검사를 하였지만 react-query 혹은 fetch를 이용하여서도 동일한 방식으로 적용될 수 있습니다.

'Next.js 개발 가이드 > 02. 코딩 가이드 및 필수 패키지' 카테고리의 다른 글

6. Middleware  (0) 2024.01.26
5. Internationalization  (0) 2024.01.24
3. tailwind-merge + clsx  (0) 2023.12.16
2. Json schema validator: Zod  (0) 2023.12.15
1. 코딩 스타일 가이드  (0) 2023.12.15
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유