@@ -4,61 +4,205 @@ description: Astro Content Collection에 새 필드 추가하는 방법
44
55# Astro Content Collection 필드 추가 가이드
66
7+ > ⚠️ ** 중요 규칙** :
8+ > 1 . 장소(Location)를 추가할 때 ** 반드시 구글맵에서 검색** 하여 정확한 위도/경도를 입력하세요! 임의로 좌표를 추측하지 마세요.
9+ > 2 . 장소명은 ** 한글을 먼저** 쓰고, 괄호 안에 일본어/영어 원어를 넣으세요. 예: ` "가와구치코 로프웨이 (河口湖ロープウェイ)" `
10+
711Location 데이터나 포스트에 새 필드를 추가할 때 수정해야 하는 파일들:
812
13+ ## 수정해야 하는 파일 요약
14+
15+ | 순서 | 파일 | 설명 |
16+ | ------| ------| ------|
17+ | 1 | ` src/content.config.ts ` | Zod 스키마에 필드 추가 |
18+ | 2 | ` src/components/map/providers/types.ts ` | TypeScript 인터페이스에 필드 추가 |
19+ | 3 | ` src/pages/posts/[slug].astro ` | 데이터 매핑에 필드 추가 |
20+ | 4 | ` src/components/map/MapContainer.tsx ` | (선택) 지도 Popup에서 렌더링 |
21+
22+ ---
23+
924## 1. Content Schema 수정 (필수!)
1025
11- ** ` src/content.config.ts ` ** - 이 파일이 실제 Astro content collection 설정 파일입니다.
26+ ** 파일:** ` src/content.config.ts `
27+
28+ > ⚠️ ** 주의** : ` src/content/config.ts ` 가 아니라 ` src/content.config.ts ` 입니다!
29+
30+ ### Location에 필드 추가
1231
1332``` typescript
1433// locationSchema에 새 필드 추가
1534const locationSchema = z .object ({
1635 name: z .string (),
1736 lat: z .number (),
1837 lng: z .number (),
19- // ... 기존 필드들
20- link: z .string ().optional (), // 새로 추가한 필드
21- visitDate: z .string ().optional (), // 새로 추가한 필드
38+ type: locationTypeSchema , // 장소 타입 enum
39+ order: z .number ().optional (), // 순서
40+ note: z .string ().optional (), // 간단한 메모
41+ link: z .string ().optional (), // 외부 링크 (빈 문자열이면 Google Maps 자동 생성)
42+ visitDate: z .string ().optional (), // 방문 날짜 (예: "10/1")
43+ contents: z .array (contentSectionSchema ).optional (), // 상세 콘텐츠 배열
44+ images: z .array (z .object ({ // 이미지 배열
45+ src: z .string (),
46+ alt: z .string ().optional (),
47+ caption: z .string ().optional (),
48+ })).optional (),
49+ country: z .string ().optional (), // 국가 (필터링용)
2250});
2351```
2452
25- > ⚠️ ** 주의** : ` src/content/config.ts ` 가 아니라 ` src/content.config.ts ` 입니다!
53+ ### 새로운 장소 타입(enum) 추가
54+
55+ ``` typescript
56+ // locationTypeSchema에 새 타입 추가
57+ const locationTypeSchema = z .enum ([
58+ ' attraction' , ' hotel' , ' restaurant' , ' cafe' , ' transport' , ' airport' ,
59+ ' shopping' , ' nature' , ' temple' , ' museum' , ' zoo' , ' theater' , ' market' ,
60+ ' beach' , ' mountain' , ' viewpoint' , ' bar' , ' palace' , ' spa' , ' gym' , ' church' ,
61+ // 여기에 새 타입 추가: 'newType',
62+ ]).optional ();
63+ ```
64+
65+ ### 포스트 자체에 필드 추가
66+
67+ ``` typescript
68+ const postsCollection = defineCollection ({
69+ type: ' content' ,
70+ schema: z .object ({
71+ title: z .string (),
72+ date: z .coerce .date (),
73+ endDate: z .coerce .date ().optional (), // 여행 종료일
74+ locations: z .array (locationSchema ),
75+ country: z .string (), // 나라 (필수)
76+ countries: z .array (z .string ()).optional (), // 여러 나라 경유 시
77+ tripType: z .array (tripTypeSchema ).optional (),
78+ tags: z .array (z .string ()).optional (),
79+ thumbnail: z .string ().optional (),
80+ excerpt: z .string ().optional (),
81+ draft: z .boolean ().optional (), // 숨김 처리 여부
82+ }),
83+ });
84+ ```
85+
86+ ---
2687
2788## 2. TypeScript 타입 수정
2889
29- ** ` src/components/map/providers/types.ts ` ** - Location 인터페이스에 필드 추가
90+ ** 파일:** ` src/components/map/providers/types.ts `
91+
92+ ### Location 인터페이스에 필드 추가
3093
3194``` typescript
3295export interface Location {
33- // ... 기존 필드들
34- link? : string ;
35- visitDate? : string ;
96+ lat: number ;
97+ lng: number ;
98+ name: string ;
99+ slug? : string ;
100+ link? : string ; // 외부 링크
101+ visitDate? : string ; // 방문 날짜
102+ order? : number ;
103+ note? : string ;
104+ type? : string ;
105+ date? : Date ;
106+ excerpt? : string ;
107+ country? : string ;
108+ // 여기에 새 필드 추가
36109}
37110```
38111
112+ ### 새 장소 타입의 아이콘 추가
113+
114+ ``` typescript
115+ export const locationTypeIcons: Record <string , string > = {
116+ attraction: ' 🏛️' ,
117+ hotel: ' 🏨' ,
118+ restaurant: ' 🍽️' ,
119+ // ...기존 타입들...
120+ church: ' ⛪' ,
121+ // 여기에 새 타입 아이콘 추가: newType: '🆕',
122+ };
123+ ```
124+
125+ ---
126+
39127## 3. 페이지에서 데이터 전달
40128
41- ** ` src/pages/posts/[slug].astro ` ** - 두 군데 수정:
129+ ** 파일:** ` src/pages/posts/[slug].astro `
130+
131+ ### 두 군데 매핑 수정 필요
42132
43133``` typescript
44- // locations 매핑에 새 필드 추가
134+ // 1️⃣ locations 매핑 (전체 데이터 - line 21~32)
45135const locations = post .data .locations .map ((loc , index ) => ({
46- // ... 기존 필드들
136+ lat: loc .lat ,
137+ lng: loc .lng ,
138+ name: loc .name ,
139+ order: loc .order ?? index + 1 ,
140+ note: loc .note ,
141+ type: loc .type ,
47142 link: loc .link ,
48143 visitDate: loc .visitDate ,
144+ contents: loc .contents ,
145+ images: loc .images ,
146+ // 여기에 새 필드 추가
49147}));
50148
51- // mapLocations에도 추가 (MapContainer로 전달 )
149+ // 2️⃣ mapLocations 매핑 (지도 컴포넌트용 - line 34~37 )
52150const mapLocations = locations .map (l => ({
53- // ... 기존 필드들
54- link: l .link ,
55- visitDate: l . visitDate ,
151+ lat: l . lat , lng: l . lng , name: l . name , order: l . order , note: l . note , type: l . type ,
152+ link: l .link , visitDate: l . visitDate ,
153+ // 여기에 새 필드 추가 (지도에서 필요한 경우)
56154}));
57155```
58156
59- ## 4. 컴포넌트에서 렌더링
157+ ### 페이지에서 렌더링
158+
159+ ``` astro
160+ <!-- link 필드 활용 예시 (line 109~119) -->
161+ <!-- 빈 문자열("")이면 Google Maps 자동 생성, 값이 있으면 해당 링크 사용 -->
162+ {loc.link !== undefined ? (
163+ <a href={loc.link === ''
164+ ? `https://www.google.com/maps/search/?api=1&query=${loc.lat},${loc.lng}`
165+ : loc.link}
166+ target="_blank" rel="noopener noreferrer" class="location-link">
167+ {loc.name}
168+ <span class="link-icon">↗</span>
169+ </a>
170+ ) : (
171+ loc.name
172+ )}
173+
174+ <!-- images 필드 활용 예시 (line 125~134) -->
175+ {loc.images && loc.images.length > 0 && (
176+ <div class="location-images">
177+ {loc.images.map(img => (
178+ <figure class="image-figure">
179+ <img src={img.src} alt={img.alt || loc.name} loading="lazy" />
180+ {img.caption && <figcaption>{img.caption}</figcaption>}
181+ </figure>
182+ ))}
183+ </div>
184+ )}
185+
186+ <!-- contents 필드 활용 예시 (line 136~145) -->
187+ {loc.contents && loc.contents.length > 0 && (
188+ <div class="location-contents">
189+ {loc.contents.map(content => (
190+ <div class="content-section">
191+ {content.heading && <h4>{content.heading}</h4>}
192+ <p>{content.text}</p>
193+ </div>
194+ ))}
195+ </div>
196+ )}
197+ ```
198+
199+ ---
200+
201+ ## 4. 컴포넌트에서 렌더링 (선택)
202+
203+ ** 파일:** ` src/components/map/MapContainer.tsx `
60204
61- ** ` src/components/map/MapContainer.tsx ` ** - Popup에서 새 필드 표시
205+ 지도 Popup에서 새 필드를 표시하려면:
62206
63207``` tsx
64208<Popup >
@@ -69,22 +213,83 @@ const mapLocations = locations.map(l => ({
69213</Popup >
70214```
71215
216+ ---
217+
72218## 5. 마크다운 파일에서 사용
73219
74- ** ` src/content/posts/*.md ` **
220+ ** 파일:** ` src/content/posts/*.md `
221+
222+ ### 완전한 Location 예시
75223
76224``` yaml
77225locations :
78- - name : " 장소명"
79- lat : 35.123
80- lng : 127.456
81- link : " https://maps.app.goo.gl/xxx"
82- visitDate : " 10/1"
226+ - name : " 콜로세움"
227+ lat : 41.8902
228+ lng : 12.4922
229+ type : attraction
230+ order : 1
231+ note : " 로마 제국의 대표적인 건축물"
232+ link : " https://maps.app.goo.gl/xxx" # 또는 빈 문자열 ""로 자동 생성
233+ visitDate : " 2/10"
234+ contents :
235+ - heading : " 💡 팁"
236+ text : " 오전 일찍 가면 줄이 짧아요"
237+ - heading : " 📸 포토 스팟"
238+ text : " 3층에서 전체 전경을 찍을 수 있어요"
239+ images :
240+ - src : " /images/rome/colosseum.jpg"
241+ alt : " 콜로세움 전경"
242+ caption : " 해질녘의 콜로세움"
83243` ` `
84244
85- ## 트러블슈팅
245+ ### link 필드 사용 패턴
246+
247+ | 값 | 동작 |
248+ |---|---|
249+ | 생략 | 장소명만 텍스트로 표시 (링크 없음) |
250+ | ` " " ` (빈 문자열) | 자동으로 Google Maps 링크 생성 (` lat,lng` 기반) |
251+ | `"https://..."` | 해당 URL로 링크 |
252+
253+ ---
254+
255+ # # 6. visitDate 기반 그룹핑 기능
256+
257+ `[slug].astro`에서 자동으로 `visitDate`별로 장소를 그룹핑합니다 :
258+
259+ ` ` ` typescript
260+ // visitDate별 그룹핑 (line 52~60)
261+ const groupedByDate = locations.reduce((acc, loc) => {
262+ const date = loc.visitDate || '날짜 미정';
263+ if (!acc[date]) acc[date] = [];
264+ acc[date].push(loc);
265+ return acc;
266+ }, {} as Record<string, typeof locations>);
267+ ` ` `
268+
269+ 이 기능을 활용하면 여행 일정별로 장소가 자동 그룹됩니다.
270+
271+ ---
272+
273+ # # 7. 트러블슈팅
274+
275+ # ## 스키마 Validation 에러 (Invalid enum value)
276+
277+ **에러 예시:**
278+ ```
279+ locations.1.type: Invalid enum value. Expected 'attraction' | ... received 'church'
280+ ```
281+
282+ **해결:**
283+ 1. `src/content.config.ts`의 `locationTypeSchema`에 새 타입 추가
284+ 2. `src/components/map/providers/types.ts`의 `locationTypeIcons`에 아이콘 추가
285+
286+ ### 데이터가 undefined로 나올 때
86287
87- 데이터가 undefined로 나오면:
882881. `src/content.config.ts` 스키마에 필드가 있는지 확인
89- 2. dev 서버 재시작 : ` npm run dev`
90- 3. 캐시 삭제 후 재시작 : ` rm -rf .astro && npm run dev`
289+ 2. `src/pages/posts/[slug].astro`의 locations 매핑에 필드를 추가했는지 확인
290+ 3. dev 서버 재시작: `npm run dev`
291+ 4. 캐시 삭제 후 재시작: `rm -rf .astro && npm run dev`
292+
293+ ### 지도에 데이터가 안 보일 때
294+
295+ `mapLocations` 매핑에도 해당 필드를 추가했는지 확인하세요.
0 commit comments