> 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="/files/d0agJDPZ8RnTSBw3bYqc" alt=""><figcaption></figcaption></figure>

<div align="left"><figure><img src="/files/iZ5aETIbPGv5Ag4BXZM9" alt=""><figcaption></figcaption></figure></div>

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


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://tech.x2bee.com/dev-guide/pjt-prepare/publish-your-docs/store-front-framewok-next.js/02./7.-loading-suspense-zod.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
