본문 바로가기
기록/개발일기

WIL: Nextjs 클라이언트 컴포넌트와 서버컴포넌트, 404페이지와 에러페이지, SEO-friendly HTML 구조 작성

by 디스코비스킷 2025. 8. 5.
반응형

1. useSearchParams를 클라이언트 컴포넌트에만 사용하라

vercel build error가 남

 ⨯ useSearchParams() should be wrapped in a suspense boundary at page "/games". Read more: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout
    at g (/vercel/path0/.next/server/chunks/956.js:7:58171)
    at l (/vercel/path0/.next/server/chunks/956.js:5:44390)

분명 useSearchParams()를 클라이언트컴포넌트내부에서만 사용하는데?

부모 컴포넌트가 서버 컴포넌트더라도, useSearchParams()를 사용하는 자식 컴포넌트가 클라이언트 컴포넌트이면 원칙적으로는 문제없지만
다음 조건 중 하나라도 충족하지 않으면 에러가 날 수 있다.

정상 작동 조건 정리

  1. useSearchParams()를 사용하는 컴포넌트 파일 상단에 'use client' 선언이 있어야 함
  2. 그 컴포넌트는 직접 렌더링되거나, 동적으로 로딩되어야 함
  3. 해당 클라이언트 컴포넌트의 렌더링이 SSR 중 실행되지 않아야 함
  4. App Router 구조에서만 동작하며, pages/ 디렉토리 기반에서는 사용 불가

내 경우, 해당 클라이언트 컴포넌트의 렌더링이 SSR 중 실행되는게 원인이다.
클라이언트 컴포넌트를 서버 컴포넌트 안에서 직접 사용하고, useSearchParams()초기에 실행되는 경우
서버 사이드 렌더링 중 useSearchParams()가 실행되어 Next.js가 에러를 발생시킨다.

해결 방법

lazy loading으로 SSR 끄기

  • Next.js 전용 lazy-loading 유틸
  • 내부적으로 dynamic import() + React.lazy() + hydration-safe 처리까지 함
import dynamic from 'next/dynamic';
const MyComponent = dynamic(() => import('./MyComponent'), { ssr: false });

export default function Page() {
  return (
    <div>
      <h1>보드게임</h1>
      <MyComponent /> {/* 이러면 CSR로만 렌더링됨 */}
    </div>
  );
}

 

 

또는,
로딩 상태까지 처리한다면

import dynamic from 'next/dynamic';
import { Suspense } from 'react';

const MyComponent = dynamic(() => import('./MyComponent'), {
  suspense: true,
});

export default function Page() {
  return (
    <Suspense fallback={<div>로딩 중...</div>}>
      <MyComponent />
    </Suspense>
  );
}
  • fallback: MyComponent가 로딩 중일 때 보여줄 UI
  • 해당 컴포넌트가 CSR + 비동기 로딩될 때 UX 향상을 위해 로딩 UI를 제공하고 싶을 때 유용하다.
  • ssr: false를 쓰면 CSR전용 컴포넌트로 만들어 동적 import하는데, suspense와 fallback을 못씀
  • suspense: trueReact.lazy()처럼 작동한다. SSR도 가능하다.(기본 true)

그런데 useSearchParams()때문에 SSR을 회피하는 목적이라면 ssr:false만으로 충분하다.... !

클라이언트 컴포넌트에서만 useSearchParams() 사용되도록 depth 분리

// page.tsx
import GameListWrapper from './GameListWrapper';

export default function Page() {
  return (
    <div>
      <GameListWrapper />
    </div>
  );
}


// GameListWrapper.tsx
'use client';

import { useSearchParams } from 'next/navigation';

export default function GameListWrapper() {
  const searchParams = useSearchParams();
  ...
}

GameListWrapper는 use client 선언된 독립된 컴포넌트이며, 직접 서버에서 프리렌더링하지 않기 때문에 에러가 나지 않는다.

2. 404페이지와 에러페이지

error.tsx와 not-found.tsx(404페이지)는 Next.js(App Router 기준)에서 전혀 다른 목적의 에러 처리 파일이다.

404페이지(not-found)

404페이지는 notFound() 함수 호출 시 자동으로 렌더링하거나
존재하지 않는 게시물, 잘못된 slug 등 리소스를 찾을 수 없는 경우에 사용한다.

not-found페이지 작성법

// app/not-found.tsx
export default function NotFoundPage() {
  return (
    <div>
      <h1>404 - 페이지를 찾을 수 없습니다</h1>
      <p>요청하신 리소스가 존재하지 않아요.</p>
    </div>
  );
}

notFound() 사용법

특정 데이터 없을 때 404 유도하는 방법이다.

import { notFound } from 'next/navigation';

export default async function Page({ params }) {
  const post = await getPost(params.slug);
  if (!post) notFound(); // 404 트리거
  return <div>{post.title}</div>;
}

자동으로 브라우저 404 상태와 연동된다.

error페이지

에러페이지는 렌더링 중 예외 처리용으로 사용된다.

  • React 컴포넌트 내부에서 발생하는 런타임 에러 처리
  • API 오류, 렌더링 중 상태 에러 등 전반적인 예외 대응
// app/error.tsx
'use client';

export default function GlobalError({ error, reset }: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div>
      <h2>에러 발생!</h2>
      <pre>{error.message}</pre>
      <button onClick={reset}>다시 시도</button>
    </div>
  );
}

자동으로 ErrorBoundary 역할을 하며,
클라이언트 컴포넌트여야 하므로 use client 필수이다.

error페이지 vs not-found페이지

구분 error.tsx not-found.tsx
처리 대상 런타임 에러, 예외 (JS 오류) 404 Not Found (존재하지 않는 페이지)
호출 방식 자동 또는 throw new Error() 자동 또는 notFound() 함수 호출
예시 상황 API 실패, 컴포넌트 오류 등 없는 slug, 페이지 없음 등
사용 위치 글로벌 or 특정 폴더 글로벌 or 특정 폴더
재시도 기능 reset() 제공 (컴포넌트 재렌더링) 없음
클라이언트 컴포넌트 필요 예 (React ErrorBoundary) 아니오 (서버 컴포넌트 가능)

Next.js(App Router 기준)에서 404 외의 다른 에러들(500, 클라이언트 렌더링 에러, 비동기 에러 등)에 대응하는 방법

상황 대응 방식 위치
전역 렌더링 에러 app/error.tsx 최상위
특정 경로 에러 app/경로/error.tsx 폴더별
404 페이지 app/not-found.tsx 최상위
API 서버 에러 try/catch + Response API Route
동적 라우팅 404 notFound() 함수 서버 컴포넌트 내부
클라이언트 컴포넌트 ErrorBoundary 사용 필요 위치

3. SEO-friendly HTML 구조 작성

SEO-friendly HTML 구조란, 검색엔진이 페이지를 정확하고 효율적으로 읽고 이해할 수 있도록 작성된 HTML 구조를 의미한다.

즉, 콘텐츠의 의미와 계층 구조를 명확하게 전달해서 검색 결과에 잘 노출되도록 돕는 구조다.

 

 

항목 설명
의미 있는 시맨틱 태그 사용 <header>, <nav>, <main>, <section>, <article>, <footer> 등 HTML5 시맨틱 태그를 사용하여 구조를 명확히 함
계층 구조 준수 제목은 <h1><h2><h3> 순서로 사용해야 하며, <h1>은 페이지당 보통 1개만 사용
alt 속성 추가 <img> 태그에는 항상 alt 속성을 추가하여 이미지의 의미를 전달
링크는 <a> 버튼이 아닌 경우에는 a 태그로 링크를 걸고, href는 절대 빠지면 안 됨(Next에서는 Link임)
중복 콘텐츠 제거 같은 내용을 여러 페이지에서 반복 노출하지 않도록 주의
텍스트 콘텐츠 확보 중요한 내용은 이미지가 아닌 텍스트로 제공해야 크롤러가 인식 가능

예시

// 예시
export default function Page() {
  return (
    <>
      <header>
        <h1>보드게임 추천 서비스</h1>
        <nav>
          <ul>
            <li><a href="/games">게임 목록</a></li>
            <li><a href="/about">서비스 소개</a></li>
          </ul>
        </nav>
      </header>
      <main>
        <section>
          <h2>인기 게임</h2>
          <article>
            <h3>카탄</h3>
            <p>전략과 협상을 즐기는 고전 명작</p>
          </article>
        </section>
      </main>
      <footer>
        <p>© 2025 보드게임 서비스</p>
      </footer>
    </>
  );
}

피할것

<div class="header">
  <div class="menu">...</div>
</div>
<div class="body">
  <div class="title">보드게임 추천</div>
  <div class="text">카탄은 ...</div>
</div>

검색엔진 최적화 관점에서 좋지 않은 구조 예시다.
의미가 불분명하기때문이다.
검색엔진은 div만 보고 구조나 내용을 제대로 이해하지 못한다.

반응형

최근댓글

최근글

© Copyright 2024 ttutta