목차
- 프로젝트 배경
- 1. 업그레이드의 시작: 패키지 업데이트
- 2. LegacyContentConfigError 발생과 원인
- 3. 마이그레이션 및 문제 해결 과정
- 4. 빌드 도중 만난 숨은 복병들
- 5. 협업의 결실: 인간-AI-AI가 만들어내는 시너지 (Tiki-Taka)
- 6. 결론: 빌드 성공과 배포 안정화
프로젝트 배경
블로그 시스템을 개선하고 최신 기능을 도입하기 위해 Astro 5에서 7로 버전 업그레이드를 진행했다. 로컬 개발 서버를 띄우고 빌드를 시도하자마자 마주한 다양한 호환성 문제와 빌드 에러들을 어떻게 극복해 나갔는지 그 여정을 상세하게 기록해 둔다.
나중에 Astro의 메이저 버전을 올리는 사람들이 더 쉽게 해결할 수 있기를 바란다.
1. 업그레이드의 시작: 패키지 업데이트
우선 @astrojs/upgrade 통합 도구를 사용하여 프로젝트의 버전들을 업그레이드했다.
@astrojs/tailwind가 최신 버전(v6)으로 유지되었고,@astrojs/sitemap은 v3.6.0에서 v3.7.3으로,- 핵심 프레임워크인
astro는 v5.15.7에서 v7.0.9로 메이저 업그레이드되었다.
2. LegacyContentConfigError 발생과 원인
버전 업그레이드 직후 npm run build를 실행했을 때 아래와 같은 치명적인 오류가 우리를 맞이했다.
[LegacyContentConfigError] Found legacy content config file in "src/content/config.ts".
Please move this file to "src/content.config.ts" and ensure each collection has a loader defined.
원인 분석
Astro v6부터는 기존의 레거시 Content Collections API(특히 type: 'content' 혹은 'data') 지원이 완전히 중단되었다. 대신 파일 시스템, 외부 API 등에서 유연하게 데이터를 가져올 수 있는 Content Layer API 사용이 강제된다.
3. 마이그레이션 및 문제 해결 과정
① 설정 파일 경로 및 구조 변경
기존 src/content/config.ts 파일을 프로젝트 루트의 src/content.config.ts로 위치를 이동하고 구조를 변경했다.
새로운 구조에서는 astro/loaders의 glob 로더를 사용하여 로컬 마크다운 파일을 수집한다.
// src/content.config.ts
import { defineCollection } from 'astro:content';
import { z } from 'astro/zod'; // z는 이제 astro:content가 아닌 astro/zod에서 가져오는 것이 권장된다.
import { glob } from 'astro/loaders';
const blogCollection = defineCollection({
loader: glob({ pattern: '**/[^_]*.md', base: "./src/content/blog" }),
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.date(),
tags: z.array(z.string()),
category: z.enum(['AI Coding', 'ARM Windows', 'Electronics', 'Essay']),
}),
});
② entry.render()의 변화
Astro v5까지는 각각의 포스트 엔트리에서 직접 entry.render() 메서드를 호출해 HTML 컴포넌트를 추출할 수 있었다. 하지만 Astro 6/7 Content Layer API에서는 더 이상 개별 엔트리에 render 메서드가 존재하지 않는다.
대신 astro:content 모듈에서 제공하는 글로벌 render 함수를 사용해야 한다.
- 기존 방식 (v5)
const { Content } = await entry.render(); - 새로운 방식 (v6/v7)
import { render } from 'astro:content'; const { Content } = await render(entry);
이에 맞춰 src/pages/blog/[...slug].astro와 src/pages/tasting/[...slug].astro 등 개별 뷰 파일들을 일괄 수정했다.
③ .slug 속성의 소멸과 .id 도입
Content Layer API를 사용하는 순간부터 엔트리들은 기본적으로 .slug 속성을 생성하지 않는다. 대신 불러오는 데이터 소스의 키나 경로를 의미하는 .id 속성을 갖게 된다.
따라서 기존 코드의 post.slug나 entry.slug를 모두 post.id 및 **entry.id**로 통일하여 정적 경로 설정(getStaticPaths) 및 링크 경로들을 수정했다.
4. 빌드 도중 만난 숨은 복병들
마이그레이션을 마친 후 다시 빌드를 돌렸을 때 생각지 못한 잔여 문제들이 튀어나왔다.
① 닫히지 않은 JSX 태그 문제
recipe.astro: 본문 설명 Paragraph 중 하나가<p>로 열린 채 닫는 태그</p>가 누락되어 빌드가 중단되었다.about.astro: 팀 리스트 레이아웃을 생성하는 루프의 내부div구조에서 닫는</div>태그가 하나 빠져 린터와 빌드 프로세스가 모두 먹통이 되는 원인이 되었다.- 해결: 중첩 태그들을 정확히 맞추어 닫는 태그를 보완했다.
② TypeScript Strict 모드와 Type Assertion
Astro 빌드가 최종 단계에 접어들었을 때 TypeScript 엄격(strict) 모드로 인해 린트 에러가 보고되었다.
The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
- 블로그 메인 태그 정렬:
tagCounts객체를reduce로 생성할 때 초기값인 빈 객체{}를 그냥 넘겨주다 보니 TypeScript가 객체의 멤버들의 타입을 추론하지 못해never나unknown간의 뺄셈 연산으로 인지하여 발생한 문제였다.- 해결: 초기값에 타입 캐스팅을 걸어주고 sort 내부의 항목도 강제 형변환하여 안전성을 확보했다.
const tagCounts = blogPosts.reduce((acc, tag) => { ... }, {} as Record<string, number>); const topTags = Object.entries(tagCounts) .sort((a, b) => (b[1] as number) - (a[1] as number))
③ 원격 배포 환경(Cloudflare Pages)의 Peer Dependency 충돌
로컬 빌드가 정상 완료된 후 Cloudflare Pages 등 외부 원격 빌드 서버로 소스를 밀어 올렸을 때, 의존성 설치(npm install) 단계에서 배포 실패를 마주했다.
- 원인: Astro 7 메이저 업그레이드를 완료했으나, 기존 Tailwind CSS v3를 지원하는 공식 라이브러리인
@astrojs/tailwind는 버전 명세상 Astro 5까지만 지원하도록 피어 의존성(Peer Dependency)이 걸려있었다. 로컬에서는 개발자가 임의로--legacy-peer-deps를 붙여 강제 설치해 넘어갔으나, 원격 빌드 서버의 자동 배포 파이프라인에서는 해당 옵션이 없어 엄격한 의존성 트리 충돌(ERESOLVE)로 중단된 것이다. - 해결: 프로젝트 루트에 .npmrc 설정 파일을 신규로 생성하고 아래 옵션을 명시했다.
이를 통해 원격 빌드 시에도 충돌 경고를 무시하고 무사히 설치를 완료할 수 있도록 보완했다. (참고로 Astro 7부터는 기존의legacy-peer-deps=true@astrojs/tailwind대신 네이티브@tailwindcss/vite플러그인을 활용한 Tailwind CSS v4 구성을 공식 권장하고 있다. 현재 프로젝트의 감성을 깨뜨리지 않고 Tailwind v3를 보존하기 위해 이번 장에서는.npmrc방식으로 호환성을 연장했다.)
5. 협업의 결실: 인간-AI-AI가 만들어내는 시너지 (Tiki-Taka)
이번 마이그레이션은 단순히 기계적인 코드 교체에 그치지 않고, 두 종류의 AI 모델 및 사람이 유기적으로 협업하며 완성도를 끌어올린 흥미로운 경험이었다.
- Antigravity의 1차 업그레이드: 우선 실행 에이전트인 Antigravity가 프로젝트 루트의
src/content.config.ts신규 생성 및 전체 페이지의 레거시 라우팅(.slug->.id), 렌더러 함수 교체와 구문 에러 수정을 전담하여 처리했다. - Web Gemini의 크로스 체크: 1차 빌드가 성공한 뒤, 작업 결과를 Web Gemini에게 교차 검증 목적으로 보여주었다. Gemini는 Astro 6/7의 최신 베스트 프랙티스로서
astro:content대신astro/zod패키지에서z를 명시적으로 가져올 것을 예리하게 조언해 주었다. - Antigravity의 2차 보완: 이 피드백을 다시 Antigravity에게 전달하여 타당성을 검토한 뒤, 실제로 코드베이스의 임포트 경로를
astro/zod로 최종 업데이트했다.
실행 엔진(Antigravity)과 전략 파트너(Gemini), 그리고 이를 조율하는 사람(User)이 함께 티키타카를 이루며 코드의 품질을 한 단계 더 단단하게 가꾸어 나간 좋은 협업 사례였다.
6. 결론: 빌드 성공과 배포 안정화
문제를 해결한 후 npm run build를 시도하자 33개의 정적 페이지 및 sitemap-index.xml이 에러 없이 깨끗하게 컴파일되었다.
메이저 업그레이드는 항상 예상하지 못한 변경 사항들을 수반하지만, 에러 메시지를 천천히 따라가며 구조를 하나하나 맞춰나가고 주변 동료(혹은 AI 파트너들)와 교차 검토를 거치다 보면 프레임워크가 제공하는 향상된 빌드 성능과 안전성을 완벽하게 확보할 수 있다.