> For the complete documentation index, see [llms.txt](https://tech.x2bee.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tech.x2bee.com/dev-guide/pjt-prepare/publish-your-docs/store-front-framewok-next.js/02./7.-loading-suspense-zod.md).

# 7. 기타 (loading, Suspense, zod 등)

이 문서는 Next.js 프로젝트에서 loading, error boundary, zod, dynamic routes, searchParams를 사용하는 예시 코드를 제공합니다.

***

## 1. Dynamic Routes

Dynamic Routes는 관행적으로 \[slug]라는 이름으로 많은 상품명에 따라 동일한 레이아웃을 가져갈 때 사용됩니다. 반드시 slug라는 이름을 사용해야 하는 것은 아니며, 가독성을 위해 아래는 `categoryname`이라는 이름으로 예시를 작성하였습니다.

다음 폴더 구조에서의 예시

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

page.tsx

{% code title="app/category/\[categoryname]/page.tsx" %}

```tsx
import React from "react";

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

export default Page;
```

{% endcode %}

브라우저에 <http://localhost:3000/category/dress> 를 입력하면 `dress`가 렌더링됩니다.

React에서 react-router-dom을 사용하면 Nested Routing 설정으로 코드가 길어질 수 있지만, Next.js에서는 위와 같이 `params`를 직접 받을 수 있어 간결합니다.

***

## 2. searchParams

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

서버 컴포넌트(페이지) 예시:

{% code title="app/somepage/page.tsx" %}

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

export default Page;
```

{% endcode %}

클라이언트 컴포넌트에서 동일한 기능 구현:

{% code title="app/somepage/client-page.tsx" %}

```tsx
"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;
```

{% endcode %}

***

## 3. zod, loading, error boundary, dynamic routes 사용 예시

폴더 구조 예시:

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

zod 설치:

```
pnpm add zod
```

아래 예시는 `http://localhost:8077/events/seoul?sp1=test&pageno=2` 접속 시

* params = { id: 'seoul' }
* searchParams = { sp1: 'test', pageno: '2' }

page.tsx (서버 컴포넌트)

{% code title="app/events/\[id]/page.tsx" %}

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

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

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

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;
```

{% endcode %}

loading.tsx에는 spinner 애니메이션이나 아이콘을 등록할 수 있고, UI 라이브러리를 이용하여 `<Skeleton />`을 등록할 수도 있습니다.

loading.tsx 예시 (간단)

{% code title="app/events/\[id]/loading.tsx" %}

```tsx
export default function Loading() {
  return (
    <div className="py-24 text-center">
      <p>Loading...</p>
    </div>
  );
}
```

{% endcode %}

error.tsx는 반드시 Client Component에서만 동작합니다.

error.tsx (클라이언트 전용)

{% code title="app/events/error.tsx" %}

```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>
  );
}
```

{% endcode %}

만약 `error.tsx`가 없다면, 상위 레벨의 에러 바운더리가 없을 경우 앱 전체가 crash되는 현상이 발생할 수 있습니다. 그러므로 각 세그먼트(폴더)에 에러 컴포넌트를 배치하여 앱 전체의 크래시를 방지하고, 사용자에게 재시도 등 복구 수단을 제공하는 것이 좋습니다.

<figure><img src="https://200425-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVEZx3rsZsIv89GPS3d2J%2Fuploads%2FAqmFJpWTtmKu8m8j53zM%2Fimg%20(21).png?alt=media&amp;token=45dd323e-dfc2-4742-9aac-d1b952b71a77" alt=""><figcaption></figcaption></figure>

<div align="left"><figure><img src="https://200425-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FVEZx3rsZsIv89GPS3d2J%2Fuploads%2F78ZH7oJ6aIdH6SjsMkBQ%2Fimg%20(22).png?alt=media&amp;token=37144072-1d45-49db-925d-ff5607fb0a56" alt=""><figcaption></figcaption></figure></div>

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