Jeonghwan's Blog
shadcn/ui 설정 때문에 삽질한 이야기

shadcn/ui 설정 때문에 삽질한 이야기

최근 회사 프로젝트에서 shadcn/ui 라이브러리를 사용하던 중 특이한 문제가 발생했어요. shadcn/ui로 생성한 컴포넌트들 중 특정 컴포넌트를 사용할 때 Hydration 에러가 발생했거든요. 이상했던 점은 동일한 shadcn/ui 컴포넌트를 개인 프로젝트에서 사용했을 때는 아무 문제 없이 잘 동작한다는 것이었어요. 분명히 같은 컴포넌트 코드인데 왜 다르게 동작할까 고민하며 원인을 파헤쳐 본 결과, 문제는 shadcn/ui 컴포넌트 생성 설정 파일인 components.jsonrsc 설정에 있었어요.

이 글에서는 shadcn/ui를 사용하며 겪었던 실제 삽질 경험을 바탕으로, shadcn/ui에 대한 소개, components.json의 주요 설정 옵션들, 그리고 문제 해결 과정에 대해 이야기하려고 해요.

shadcn/ui는 무엇일까?

shadcn/ui는 Tailwind CSS 기반의 UI 컴포넌트 세트예요. 다른 UI 라이브러리와 다른 점은, “설치해서 가져다 쓰는 컴포넌트”가 아니라 내 프로젝트에 직접 코드로 생성해 주는 방식이라는 거예요.

즉, 컴포넌트 코드를 그대로 내 저장소에 넣고, 내가 원하는 대로 수정하면서 쓸 수 있다는 장점이 있어요.

components.json 설정

shadcn/ui를 프로젝트에 세팅하면 루트에 components.json 파일이 생성돼요. 이 파일은 shadcn/ui 컴포넌트를 프로젝트의 어떤 경로에 생성할지, 어떤 스타일 프리셋을 사용할지, 그리고 React Server Components(RSC) 환경을 전제로 할지 등을 정의하는 설정 파일이에요.

대표적인 설정들을 하나씩 살펴보면 다음과 같아요.

$schema

JSON Schema를 지정해서 자동 완성과 검증을 지원해요. VSCode 같은 에디터에서 설정 키와 값의 타입 힌트를 받을 수 있죠.

// components.json
{
  "$schema": "https://ui.shadcn.com/schema.json"
}

style

컴포넌트 스타일 프리셋을 지정해요.

// components.json
{
  "style": "new-york"
}

rsc

React Server Components(RSC) 전제를 할지 여부예요.

  • true: App Router 환경을 전제 → 필요한 경우 'use client'를 자동으로 추가

  • false: Pages Router/CSR 전제를 가정 → 'use client'를 자동으로 붙이지 않음

    👉 이번에 Hydration 에러가 난 핵심 원인이었어요.

// components.json
{
  "rsc": `true` | `false`
}

tsx

컴포넌트 파일을 .tsx 확장자로 생성할지 .jsx로 생성할지 결정하는 설정이에요.

  • true: TypeScript 프로젝트에서는 true로 선언하여 .tsx 확장자로 생성
  • false: JavaScript 프로젝트에서는 false로 선언하여 .jsx 확장자로 생성
// components.json
{
  "tsx": `true` | `false`
}

aliases

컴포넌트를 생성할 때 import 경로를 짧게 쓰도록 별칭을 지정해요.

(실제 tsconfig.jsonpaths와 맞춰주어야 해요.)

  • utils: 공통 유틸리티 함수(예: cn, 포맷터 등)의 import 경로를 지정
  • components: 프로젝트에서 사용하는 UI 컴포넌트들의 기본 import 경로를 지정
  • ui: 버튼, 모달, 카드 같은 UI Primitive 전용 경로를 지정
  • lib: API, 데이터 처리, 서비스 로직 등 라이브러리성 코드의 import 경로를 지정
  • hooks: React 커스텀 훅들의 import 경로를 지정
// components.json
{
  "aliases": {
    "utils": "@/lib/utils",
    "components": "@/components",
    "ui": "@/app/ui",
    "lib": "@/lib",
    "hooks": "@/hooks"
  }
}

tailwind

shadcn/ui 컴포넌트가 사용할 Tailwind 경로와 스타일 방식을 정하는 옵션이에요.

  • config: Tailwind 설정 파일 경로
  • css: 전역 스타일 파일 경로
  • baseColor: 기본 테마 컬러
  • cssVariables: Tailwind 색상 팔레트를 CSS 변수 형태로 자동 생성할지 여부를 결정
  • prefix: Tailwind 클래스 이름 앞에 접두사(prefix)를 붙이는 옵션
// components.json
{
	"tailwind": {
    "config": "tailwind.config.js" | "tailwind.config.ts",
    "css": "styles/global.css",
    "baseColor": "gray" | "neutral" | "slate" | "stone" | "zinc",
    "cssVariables": `true` | `false`,
    "prefix": "tw-"
	}
}

문제의 원인이었던 rsc 설정

문제가 발생했던 프로젝트의 components.json 은 다음과 같이 되어 있었어요.

{
  ...
  "rsc": false
}

Next.js App Router 환경에서 rsc: false 로 설정하면, 'use client' 선언이 필요한 컴포넌트에 자동으로 'use client' 선언이 누락되어요. 그러면 서버 컴포넌트로 잘못 해석되면서, 컴포넌트 내부에서 useState, useEffect 같은 클라이언트 훅을 사용할 때 결국 Hydration mismatch 에러가 발생하는 거죠.

반대로, 개인 프로젝트에서도 동일하게 Next.js App Router 환경이었지만 rsc: true로 설정되어 있어서 'use client' 선언이 자동으로 추가되면서 문제가 발생하지 않았던 것이죠.

문제 해결 과정

아래에서는 이번 문제를 해결하기 위해 단계별로 진행했던 과정을 설명해 드릴게요.

1. Hydration 에러 원인 파악

먼저 Hydration 에러가 발생할 수 있는 주된 원인들을 정리했어요. 서버와 클라이언트 렌더링 결과 불일치, 브라우저 전용 API 사용, 실행할 때마다 달라지는 값(Date.now, Math.random) 활용, 클리이언트 컴포넌트에 'use client' 선언 누락 같은 경우가 대표적이죠.

이후 문제가 된 컴포넌트 코드를 열어 이런 원인들이 해당되는 부분이 있는지 하나씩 대입해 가며 탐색했어요. 그 결과 'use client' 선언 누락이 발견되었죠.

2. 근본 원인 유추

'use client'가 누락되었는지 의문이 들었어요. 개인 프로젝트에서는 동일한 shadcn/ui 컴포넌트를 생성했을 때 'use client'가 정상적으로 선언되었는데, 회사 프로젝트에서만 이 선언이 누락되었죠. 같은 라이브러리를 사용하는데 결과가 다르다는 점에서 shadcn/ui의 설정 파일(components.json)의 문제일 가능성을 유추했어요.

3. 근본 원인 파악

개인 프로젝트와 회사 프로젝트의 components.json 설정을 비교해 보니, 유일한 차이는 바로 rsc 값이 truefalse냐였어요. 개인 프로젝트는 rsc: true라서 shadcn/ui가 자동으로 'use client' 선언이 필요한 컴포넌트에는 'use client'가 같이 선언되어 생성되었지만, 회사 프로젝트는 rsc: false로 되어 있어 'use client'가 필요한 컴포넌트에도 'use client'가 선언되지 않은 상태로 코드가 생성된 거죠. 결국 이 작은 설정 차이가 Hydration 에러의 원인으로 이어졌음을 확인할 수 있었어요.

4. 해결

components.jsonrsc 값을 true로 수정하고 문제가 된 컴포넌트를 다시 생성했더니 'use client'가 정상적으로 붙으면서 Hydration 에러가 사라졌어요.

마무리

이번 문제 해결 경험은 단순히 Hydration 에러 하나를 해결한 것에서 그치지 않았어요. shadcn/ui의 components.json 같은 설정 파일이 프로젝트 전반의 동작에 얼마나 큰 영향을 주는지를 몸소 깨닫게 해준 계기였어요. 특히 회사 프로젝트와 개인 프로젝트의 차이는 rsc 설정값 하나뿐이었는데, 그 작은 차이가 컴포넌트의 동작 여부와 에러 발생을 갈랐던 거죠.

이 과정을 겪으면서 “라이브러리를 사용할 때는 단순히 사용하는 컴포넌트 또는 함수 코드만 살피는 게 아니라, 그 라이브러리에 설정 파일이 있다면 그 설정 파일들이 어떤 역할을 하고 프로젝트 환경과 어떻게 맞물리는지 꼼꼼히 확인하는 습관이 필요하다.”는 점을 크게 반성하며 느꼈어요.

혹시 여러분도 shadcn/ui를 사용하다가 비슷한 Hydration 에러로 시간을 허비하고 있다면, 저와 같은 원인(components.jsonrsc 설정)을 놓치고 있는 것은 아닌지 꼭 한번 확인해 보세요.

참고 자료