“차트가 없는 페이지는 0KB의 자바스크립트를 유지하고, 차트가 있는 페이지는 인간에게는 인터랙티브 캔버스를, 검색엔진과 AI에게는 시맨틱 데이터 테이블을 동시에 제공한다.”
테크니컬 마케팅과 웹 아키텍처 리서치 아티클을 발행하다 보면 Core Web Vitals 벤치마크, 오가닉 검색 전환 퍼널, 다중 채널 유입 여정(User Journey) 등 복잡한 정량 데이터를 독자에게 전달해야 하는 순간이 찾아옵니다.
본 문서는 Astro 공식 블로그의 2023 웹 프레임워크 성능 리포트에서 영감을 받아, mktg.kr 환경에 최적화된 범용 시맨틱 차트 시스템(<Chart />)을 기획 단계부터 기술 검토, 아키텍처 설계, 마케팅 특화 플러그인 통합, 그리고 최종 구현까지 완성한 전 과정의 엔지니어링 경험기이자 실전 가이드입니다.
1. 기획 배경: 왜 정적 이미지 대신 인터랙티브 차트인가?
기존 블로그 포스트에서 차트를 삽입하는 가장 흔한 방법은 엑셀이나 디자인 툴에서 캡처한 정적 이미지(.png, .webp) 를 올리는 것입니다. 하지만 기술 매거진이자 마케팅 엔지니어링 플랫폼인 MKTG Edge에서는 정적 차트 이미지가 가진 명확한 한계를 해결해야 했습니다.
1.1 정적 이미지 차트의 4가지 한계점
- 반응형 가독성 저하: 데스크톱 화면에 맞춰 캡처한 차트 이미지는 모바일 뷰포트에서 글씨가 너무 작아져 읽을 수 없거나, 반대로 모바일에 맞추면 데스크톱에서 비대해집니다.
- 다크/라이트 모드 이질감: 사이트 테마를 다크 모드로 전환했을 때 흰색 배경의 정적 차트 이미지가 눈부시게 떠 보이거나, 투명 배경 PNG의 검은색 축 글씨가 어두운 배경에 묻혀 보이지 않는 문제가 발생합니다.
- 데이터 탐색(Exploration) 불가: 여러 데이터셋이 겹쳐 있을 때 특정 항목만 필터링하거나 마우스를 올려 정확한 소수점 수치(Tooltip)를 확인할 수 없습니다.
- 검색엔진 및 AI 크롤러(GEO) 데이터 비가시성: 이미지 픽셀 안에 갇힌 숫자는 Googlebot, Naver Yeti, 그리고 Perplexity·ChatGPT 같은 LLM 기반 검색 에이전트가 구조화된 정량 데이터로 추출하기 어렵습니다.
2. 벤치마킹 및 기술 스택 의사결정 (Astro.build 분석)
가장 먼저 레퍼런스로 삼은 곳은 Astro 공식 블로그의 2023 Web Framework Performance Report 페이지였습니다. 실제 오픈소스 저장소(withastro/astro.build)의 구현 방식을 역공학(Reverse Engineering)하여 우리의 요구사항과 대조했습니다.
2.1 Astro 공식 블로그의 차트 구현 구조
Astro 팀은 리포트 본문(.mdx)에서 각 차트를 개별 .astro 컴포넌트(CWVCulminativeChart.astro, CostOfJSChart.astro 등)로 분리하고, 내부에서 Chart.js v4 (chart.js/auto) 를 <script> 태그로 로드하여 <canvas> 요소를 렌더링하고 있었습니다.
- 장점: Astro의 번들링 특성상 해당 컴포넌트가 사용된 페이지에서만
chart.js스크립트가 로드되므로, 차트가 없는 다른 블로그 글에는 단1 byte의 자바스크립트도 추가되지 않습니다. - 개선이 필요했던 지점: Astro 공식 블로그는 차트마다 별도의
.astro파일을 하드코딩으로 생성(CWVFIDChart.astro,CWVLCPChart.astro등)하여 재사용성이 낮았고, 일반 막대(bar)와 버블(bubble) 차트 외에 마케팅 분석에 필수적인 퍼널(Funnel), 고객 여정(Sankey), 트리맵(Treemap) 이 없었습니다.
2.2 구현 옵션 검토 및 최종 선택
우리는 세 가지 아키텍처 후보군을 놓고 기술 검토를 진행했습니다:
| 구분 | 옵션 1: Chart.js + 확장 플러그인 통합 (채택) | 옵션 2: 순수 SVG/CSS 정적 차트 | 옵션 3: D3.js / Recharts (React Island) |
|---|---|---|---|
| 지원 차트 범위 | 기본 8종 + Funnel, Sankey, Treemap 전종 지원 | 단순 막대/가로 바 차트에 한정 | 높은 자유도 (단, 구현 복잡도 높음) |
| 인터랙션 (툴팁/범례) | 호버 툴팁, 범례 토글 필터링, 애니메이션 기본 내장 | 제한적 (CSS 호버 수준) | 직접 이벤트 핸들러 구현 필요 |
| 번들 및 성능 영향 | 차트 사용 페이지에만 온디맨드 로드 (일반 페이지 0KB) | Zero-JS (가장 가벼움) | React 런타임 하이드레이션(client:load) 비용 발생 |
| 작성 편의성 (DX) | 단일 <Chart /> 태그에 Props로 JSON 데이터만 전달 | 마크업 구조가 단순함 | 차트별 커스텀 컴포넌트 개발 필요 |
검토 끝에 옵션 1 (Chart.js v4 + 마케팅 확장 플러그인 3종을 결합한 단일 <Chart /> Astro 컴포넌트) 을 최종 채택했습니다. React 하이드레이션(client:visible) 없이 순수 Astro <script> 모듈로 구동하여 런타임 오버헤드를 최소화하면서도, 마케팅 리서치에 필요한 고급 시각화를 모두 포괄할 수 있기 때문입니다.
3. 핵심 아키텍처 설계 및 엔지니어링 과제 해결
단순히 Chart.js를 화면에 띄우는 것을 넘어, mktg.kr의 엄격한 기술 표준에 맞추기 위해 다음 4가지 핵심 엔지니어링 설계를 적용했습니다.
[MDX 아티클 / Docs 본문: <Chart type="..." data={...} /> 호출]
│
├─► [1. 서버/빌드 타임 (SSR / SSG)]
│ ├─ <figure> + <figcaption> 시맨틱 컨테이너 렌더링
│ ├─ <canvas data-chart-config={JSON}> 직렬화 주입
│ └─ <canvas> 내부에 크롤러/스크린리더용 정적 <table> 자동 생성 (SEO/GEO 폴백)
│
└─► [2. 클라이언트 런타임 (브라우저)]
├─ Chart.js v4 + Funnel / Sankey / Treemap 컨트롤러 등록
├─ valueFormat('percent', 'currency', 'kb', 'ms') 수치 자동 포맷팅
└─ MutationObserver 기반 다크/라이트 모드 실시간 테마 동기화
3.1 전역 MDX 컴포넌트 주입 (Zero-Import Authoring)
매번 글을 쓸 때마다 상단에 import Chart from ...를 작성하는 번거로움을 없애고, Vite 개발 서버에서 별칭(@/) import 시 발생할 수 있는 모듈 해석 오류를 원천 방지하기 위해 블로그(src/pages/blog/[slug].astro)와 Docs(src/pages/docs/[...slug].astro)의 mdxComponents 객체에 <Chart />를 전역 등록했습니다. 이제 어떤 .mdx 파일에서든 태그만 바로 호출하면 됩니다.
3.2 검색엔진(SEO) 및 AI 에이전트(GEO)를 위한 시맨틱 <table> 폴백
HTML5 <canvas>는 기본적으로 비트맵 캔버스이므로 크롤러가 내부 텍스트를 읽지 못합니다. 이를 해결하기 위해 <canvas> 여는 태그와 닫는 태그 사이에 data.labels와 data.datasets를 기반으로 표준 HTML <table> 마크업이 서버 사이드에서 자동 렌더링되도록 설계했습니다.
- 일반 브라우저 사용자에게는 유려한 인터랙티브
<canvas>차트가 보입니다. - 자바스크립트를 실행하지 않는 검색 크롤러, AI 봇, 시각장애인용 스크린 리더는
<canvas>내부의 시맨틱<table>을 통해 모든 수치와 라벨을 100% 손실 없이 읽어냅니다.
3.3 MutationObserver 기반 실시간 다크/라이트 모드 전환
<canvas>에 그려진 픽셀은 CSS 클래스가 바뀐다고 해서 스스로 색상이 변하지 않습니다. 이를 해결하기 위해 document.documentElement의 class 및 data-theme 속성 변화를 감지하는 MutationObserver를 연결했습니다. 사용자가 상단 헤더의 테마 토글 버튼을 누르는 즉시 차트가 현재 테마 토큰(그리드 투명도, 텍스트 대비, 툴팁 배경색)을 읽어 매끄럽게 리렌더링됩니다.
3.4 직관적인 수치 포맷터 (valueFormat 프리셋)
MDX 파일 내부는 직렬화된 데이터만 전달할 수 있어 복잡한 자바스크립트 콜백 함수를 매번 넘기기 어렵습니다. 대신 valueFormat="percent" | "currency" | "kb" | "ms" | "number" 속성 하나만 지정하면:
'percent':0.67을 넣든67을 넣든 지능적으로 판별하여67%로 축 눈금과 툴팁을 자동 변환합니다.'currency':1500000→₩1,500,000(원화 통화 표기)'kb'/'ms':277.4→277.4 KB/120 ms단위를 자동으로 붙여줍니다.
4. 차트 유형별 라이브 데모 및 입력 가이드
실제 구축된 <Chart /> 컴포넌트로 생성한 6가지 핵심 차트(막대, 다중 분위수 막대, 버블, 마케팅 퍼널, 고객 여정 Sankey, 키워드 트리맵) 의 라이브 결과물과 입력 코드입니다. 각 차트 위에 마우스를 올리거나 범례를 클릭해 보세요.
4.1 <Chart /> 공통 Props 명세
| 속성명 (Prop) | 허용 값 | 기본값 | 설명 |
|---|---|---|---|
| type | 'bar' | 'horizontalBar' | 'line' | 'area' | 'bubble' | 'scatter' | 'doughnut' | 'pie' | 'radar' | 'polarArea' | 'funnel' | 'sankey' | 'treemap' | 'bar' | 렌더링할 차트 종류 |
| title | string | '' | 차트 카드 상단 타이틀 라벨 |
| subtitle | string | '' | 타이틀 하단 보조 설명문 |
| caption | string | '' | 하단 시맨틱 <figcaption> 캡션 및 출처 |
| data | { labels?: string[], datasets: any[] } | 필수 | 시각화할 데이터 객체 |
| valueFormat | 'number' | 'percent' | 'currency' | 'kb' | 'ms' | 'number' | 축 및 툴팁 수치 자동 포맷팅 |
| yMax | number | 자동 | Y축(수평 막대는 X축) 최대값 고정 |
| legend | boolean | 'top' | 'bottom' | 'left' | 'right' | 자동 | 범례 표시 여부 및 위치 |
| stacked | boolean | false | 누적 막대/꺾은선 여부 |
| height | number | string | 340 | 차트 캔버스 높이 (예: 320, '380px') |
4.2 [기본 1] 단일 막대 차트 (type="bar") — Core Web Vitals 통과율 비교
Astro 공식 리포트의 첫 번째 차트를 그대로 재현한 구성입니다. valueFormat="percent"와 yMax={1}만 지정하면 0~1 사이의 비율 데이터가 0% ~ 100% 스케일로 깔끔하게 그려집니다.
HTTP Archive 및 CrUX 실사용자 데이터 기준 웹 프레임워크별 CWV 통과율
<Chart
type="bar"
title="% Sites Passing Core Web Vitals"
subtitle="HTTP Archive 및 CrUX 실사용자 데이터 기준 웹 프레임워크별 CWV 통과율"
caption="그림 1: 웹 프레임워크별 Core Web Vitals 통과 비율 (출처: CrUX & HTTP Archive)"
valueFormat="percent"
yMax={1}
data={{
labels: ["Astro", "SvelteKit", "Gatsby", "Remix", "WordPress", "Next.js", "Nuxt"],
datasets: [
{
label: "CWV 통과율",
data: [0.67, 0.45, 0.394, 0.358, 0.304, 0.275, 0.202]
}
]
}}
/>
4.3 [기본 2] 다중 그룹 막대 차트 (type="bar", 복수 datasets)
datasets 배열에 여러 지표를 나열하면 자동으로 MKTG Edge 시그니처 컬러 팔레트와 상단 범례(Legend)가 활성화됩니다. 상단 범례(p25, p50 등)를 클릭하면 특정 분위수만 켜거나 끌 수 있습니다.
프레임워크별 Lighthouse 성능 점수 분위수(p25, p50, p75, p90) 분포
<Chart
type="bar"
title="Lighthouse Performance Score, Breakdown"
subtitle="프레임워크별 Lighthouse 성능 점수 분위수(p25, p50, p75, p90) 분포"
caption="그림 2: 프레임워크별 Lighthouse 성능 분위수 비교"
valueFormat="percent"
yMax={1}
data={{
labels: ["Astro", "SvelteKit", "Remix", "Gatsby", "Next.js", "Nuxt"],
datasets: [
{ label: "p25", data: [0.44, 0.33, 0.29, 0.18, 0.19, 0.16] },
{ label: "p50 (Median)", data: [0.65, 0.52, 0.46, 0.32, 0.30, 0.25] },
{ label: "p75", data: [0.87, 0.73, 0.59, 0.55, 0.47, 0.37] },
{ label: "p90", data: [0.98, 0.89, 0.80, 0.68, 0.63, 0.54] }
]
}}
/>
4.4 [기본 3] 버블 차트 (type="bubble") — 다변수 상관관계 시각화
가로축(x: 페이지당 JS 용량 KB), 세로축(y: CWV 통과율), 그리고 버블 크기(r)를 결합하여 자바스크립트 번들 무게가 웹 바이탈 성능에 미치는 영향을 직관적으로 보여줍니다.
X축: 페이지당 평균 JS 전송량(KB) / Y축: CWV 통과율(%)
<Chart
type="bubble"
title="Median KB of JavaScript vs. % Sites Passing CWV"
subtitle="X축: 페이지당 평균 JS 전송량(KB) / Y축: CWV 통과율(%)"
caption="그림 3: 자바스크립트 번들 용량이 증가할수록 CWV 통과율이 하락하는 음의 상관관계"
valueFormat="percent"
legend="left"
data={{
datasets: [
{ label: "Astro", data: [{ x: 277, y: 0.67, r: 11 }] },
{ label: "SvelteKit", data: [{ x: 324, y: 0.45, r: 10 }] },
{ label: "Remix", data: [{ x: 569, y: 0.36, r: 10 }] },
{ label: "Gatsby", data: [{ x: 846, y: 0.39, r: 10 }] },
{ label: "Next.js", data: [{ x: 885, y: 0.27, r: 11 }] },
{ label: "Nuxt", data: [{ x: 928, y: 0.20, r: 10 }] }
]
}}
/>
4.5 [마케팅 확장 1] 마케팅 전환 퍼널 차트 (type="funnel")
chartjs-chart-funnel 플러그인을 통합하여 구현한 전환 퍼널 차트입니다. 검색 노출(Impression) → 오가닉 클릭(Click) → 아티클 완독(Engaged) → 리드 전환(Conversion)으로 이어지는 단계별 볼륨과 이탈 구조를 표현할 때 사용합니다.
오가닉 검색 노출부터 리드 전환까지의 4단계 마케팅 퍼널
<Chart
type="funnel"
title="Organic Search to Lead Conversion Funnel"
subtitle="오가닉 검색 노출부터 리드 전환까지의 4단계 마케팅 퍼널"
caption="그림 4: 테크니컬 아티클 오가닉 유입 후 뉴스레터/문의 전환 퍼널"
height={300}
data={{
labels: [
"1단계: SERP 노출 (Impressions)",
"2단계: 오가닉 클릭 유입 (Clicks)",
"3단계: 아티클 75% 완독 (Engaged)",
"4단계: 뉴스레터/리드 전환 (Converted)"
],
datasets: [
{
label: "사용자 수",
data: [10000, 3200, 1450, 420]
}
]
}}
/>
4.6 [마케팅 확장 2] 고객 여정 흐름도 / 생키 다이어그램 (type="sankey")
chartjs-chart-sankey 플러그인을 통해 유입 검색엔진(Google, AI 검색, NAVER)에서 첫 랜딩 콘텐츠(블로그, 용어사전, Docs)를 거쳐 최종 전환에 이르는 다대다(Many-to-Many) 트래픽 흐름을 시각화합니다. { from, to, flow } 배열만 입력하면 노드 위치와 곡선 가중치가 자동 계산됩니다.
유입 검색엔진 → 첫 랜딩 콘텐츠 → 전환 액션까지의 트래픽 흐름도
<Chart
type="sankey"
title="Multi-Channel User Journey Flow (Sankey)"
subtitle="유입 검색엔진 → 첫 랜딩 콘텐츠 → 전환 액션까지의 트래픽 흐름도"
caption="그림 5: 검색 및 AI 엔진 유입 사용자의 콘텐츠 소비 및 전환 여정"
height={340}
data={{
datasets: [
{
label: "트래픽 흐름",
data: [
{ from: "Google 검색", to: "기술 블로그", flow: 55 },
{ from: "Google 검색", to: "용어사전 (Glossary)", flow: 30 },
{ from: "AI 검색 (Perplexity/ChatGPT)", to: "Docs 플레이북", flow: 25 },
{ from: "NAVER 검색", to: "기술 블로그", flow: 20 },
{ from: "기술 블로그", to: "Docs 플레이북", flow: 35 },
{ from: "기술 블로그", to: "리드 전환", flow: 40 },
{ from: "용어사전 (Glossary)", to: "기술 블로그", flow: 20 },
{ from: "Docs 플레이북", to: "리드 전환", flow: 45 }
]
}
]
}}
/>
4.7 [마케팅 확장 3] 키워드 볼륨 트리맵 (type="treemap")
chartjs-chart-treemap 플러그인을 활용하여 토픽 클러스터별 월간 검색량이나 카테고리별 트래픽 점유율을 사각형 면적 비례로 보여주는 트리맵입니다. tree 배열에 { label, value } 객체를 나열하고 key: "value"를 지정하면 됩니다.
핵심 토픽 클러스터별 월간 오가닉 검색 볼륨 비중
<Chart
type="treemap"
title="Topic Cluster Monthly Search Volume Treemap"
subtitle="핵심 토픽 클러스터별 월간 오가닉 검색 볼륨 비중"
caption="그림 6: MKTG Edge 주요 기술 토픽 클러스터별 검색 수요 비중"
height={320}
data={{
datasets: [
{
label: "월간 검색 볼륨",
tree: [
{ label: "Technical SEO & Schema", value: 4200 },
{ label: "Astro & Edge SSR", value: 3100 },
{ label: "GEO / AI Search Optimization", value: 2800 },
{ label: "Core Web Vitals (INP/LCP)", value: 2100 },
{ label: "Headless CMS Architecture", value: 1650 },
{ label: "GA4 & Server-side GTM", value: 1400 }
],
key: "value"
}
]
}}
/>
5. Hugo 등 타 정적 사이트 생성기(SSG) 확장성 및 요약
이번에 구축한 차트 시스템은 특정 UI 프레임워크(React, Vue 등)에 종속되지 않은 표준 HTML5 <canvas> + 바닐라 JavaScript 모듈 구조로 설계되었습니다. 따라서 추후 Hugo(휴고) 나 11ty 같은 다른 정적 사이트 생성기에서도 동일한 데이터 규격을 그대로 재사용할 수 있습니다.
- Astro (
mktg.kr): MDX 본문에서<Chart type="funnel" data={...} />컴포넌트로 호출 - Hugo:
layouts/shortcodes/chart.html숏코드를 생성하고 마크다운 본문에서{{< chart type="funnel" >}} ... {{< /chart >}}형태로 호출 (Page.HasShortcode "chart"조건문으로 차트가 포함된 페이지에서만 스크립트 로드)
이제 MKTG Edge의 모든 리서치 아티클과 엔지니어링 플레이북에서 정적 캡처 이미지 대신 성능(CWV), 시맨틱 검색 최적화(SEO/GEO), 그리고 다크 모드 디자인 일관성을 모두 충족하는 인터랙티브 차트를 즉시 활용할 수 있습니다.