Nuxt Content v3 데이터 모델과 Query 시스템 이해
🧭 1. 들어가는 글: Nuxt Content v3의 탄생 배경
과거 Nuxt 3 시절의 Nuxt Content v2는 마크다운(.md) 파일을 실시간으로 읽고 메모리 내에서 인덱싱하는 가벼운 파서 역할을 수행했다. 하지만 프로젝트 규모가 커지고 문서의 양이 방대해질수록 빌드 속도 저하와 데이터 쿼리의 한계가 명확해졌다.
Nuxt Content v3는 Nuxt 4 아키텍처에 맞춰 바닥부터 완전히 재설계되었다. 핵심 골자는 정적 파일들을 파싱한 뒤, 내부적으로 강력한 SQLite 데이터베이스 엔진을 구축하여 데이터를 관계형 DB처럼 관리하는 구조적 대격변을 이뤄냈다는 점이다.
🏗️ 2. 아키텍처 및 핵심 메커니즘
Nuxt Content v3의 데이터 처리 파이프라인은 다음과 같은 3단계 레이어로 작동한다.
[ 마크다운 파일 (.md) ]
│ (빌드 / 개발 서버 구동 시점 파싱)
▼
[ Nitro SQLite 임베디드 DB (인덱싱 완료) ] ──▶ content.config.ts (스키마 제어)
│ (런타임 시점 높은 효율의 SQL 조회)
▼
[ queryCollection() API 호출 ]
① 로컬 파일의 데이터베이스화
프로젝트가 실행되면 Nitro 엔진은 content/ 디렉토리 내의 모든 마크다운 파일과 Front-matter 설정을 스캔한다. 이 정보들은 휘발성 텍스트가 아닌, 엄격하게 규격화된 **SQLite 데이터베이스 테이블의 로우(Row)**로 치환되어 메모리에 상주한다.
② content.config.ts를 통한 스키마(Schema) 제어
v3로 넘어오면서 가장 엄격해진 부분이다. 이전 버전처럼 프론트매터에 임의의 필드(예: date, author)를 넣는다고 해서 쿼리 엔진이 자동으로 인지하지 못한다.
반드시 데이터베이스 테이블의 컬럼을 선언하듯, content.config.ts 파일에서 defineCollection과 Zod 라이브러리를 활용해 스키마를 선언해주어야 정상적인 인덱싱 및 정렬 처리가 가능하다.
🚨 실무 트러블슈팅 팁 (No Such Column Error) 만약 특정 다국어 폴더(
en/)에는date필드가 누락되어 있고, 특정 폴더(ko/)에만 존재할 때, 스키마에 이를 명시하지 않고 정렬(.order('date'))을 시도하면 데이터베이스 단에서no such column: "date"에러를 뿜으며 빌드가 터진다. 이를 방지하기 위해 반드시.optional()구조로 컬럼의 존재를 DB 인프라에 사전 등록해 주어야 한다.
🛠️ 3. Nuxt 4와 Nuxt Content v3의 명확한 역할 분담
코드를 작성할 때 가장 혼동하기 쉬운 두 컴포저블(useAsyncData와 queryCollection)의 역할은 명확하게 분리되어 있다.
const { data: posts } = await useAsyncData('posts_key', () =>
queryCollection('docs_ko').order('date', 'DESC').all()
)
🤝 역할 분담 테이블
| 기능 컴포저블 | 소속 생태계 | 핵심 역할 및 기능 |
|---|---|---|
queryCollection() | Nuxt Content v3 | 내장 SQLite DB에서 타겟 컬렉션 데이터를 정렬/필터링하여 가져오는 행위 자체를 담당 (SQL의 SELECT 문 역할) |
useAsyncData() | Nuxt 4 (Core) | 데이터 패칭 흐름 제어. 서버(SSR)에서 긁어온 데이터를 캐싱하여, 브라우저가 하이드레이션될 때 중복 API 요청이 발생하지 않도록 방어 |
📦 4. 실무에서 주로 사용되는 신형 쿼리 문법
v2의 .sort(), .where() 형태의 체이닝 문법은 v3에서 완벽한 데이터베이스 친화적(SQL-Like) 메서드로 대체되었다.
① 최신 날짜순 전체 조회 (.order(), .all())
컬렉션 명칭을 인자로 받아 데이터를 정렬한 뒤 전체 데이터를 객체 배열로 반환한다.
const { data: articles } = await useAsyncData('articles', () =>
queryCollection('docs_ko')
.order('date', 'DESC') // 👈 첫 번째 인자는 컬럼명, 두 번째 인자는 방향(DESC/ASC)
.all()
)
② 특정 조건 검색 (.where(), .first())
단 한 건의 고유 문서만 매핑하여 가져올 때는 끝에 .first()를 결합한다.
const { data: page } = await useAsyncData('current_page', () =>
queryCollection('docs_ko')
.where('path', '=', '/project/brolog') // 👈 정확한 비교 연산자 주입 구조
.first()
)
🧠 5. 결론 및 실무 인사이트
Nuxt 4와 Nuxt Content v3의 결합은 단순한 버전 업그레이드가 아니다. 파일 기반 컨텐츠 관리 시스템을 엔터프라이즈급 DB 아키텍처 수준으로 격상시킨 진화다.
- 엄격한 스키마 정의 필수: 마크다운을 작성할 때 프론트매터 규칙이 엄격해진 만큼, 공통 스키마 확장을 통해 데이터 무결성을 먼저 확보해야 정렬 및 필터링 과정에서 런타임 에러를 방지할 수 있다.
- 구조적 이점: 내장 컴포넌트나 코드 블록 내부의 문자열을 파서가 기만적으로 추적하여
/undefined404 링크 에러를 터트리는 독특한 파싱 메커니즘이 존재하므로, 본문 작성 시 태그(<img>등) 표현이나 에셋 경로는 철저히 백틱 처리하거나 이스케이프하여 작성하는 숙련도가 필요하다.
