feat: webforai.kr 방문 측정 (GTM·GA4) 연결과 처리방침 반영
- discovery 빌드에만 GTM(GTM-WQNTZVZH) 스니펫을 넣는다. GA4(G-013Z3BFY35)와 키 이벤트는 컨테이너 안에서 설정한다 - 문의 접수 성공 시 dataLayer 에 generate_lead(lead_source) 를 보낸다. 개인정보는 넣지 않는다 - GTM 가져오기 파일(docs/analytics/gtm_webforai_import.json): GA4 기본 태그, generate_lead·click_tel·click_email - 개인정보처리방침 제1·5·8조에 Google 애널리틱스 분석 쿠키, 수집 항목, 국외 이전, 거부 방법을 반영한다 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f03c6a5b90
commit
c90aef654f
268
docs/analytics/gtm_webforai_import.json
Normal file
268
docs/analytics/gtm_webforai_import.json
Normal file
@ -0,0 +1,268 @@
|
||||
{
|
||||
"exportFormatVersion": 2,
|
||||
"exportTime": "2026-10-07 12:00:00",
|
||||
"containerVersion": {
|
||||
"accountId": "6381018301",
|
||||
"containerId": "266335040",
|
||||
"containerVersionId": "0",
|
||||
"container": {
|
||||
"accountId": "6381018301",
|
||||
"containerId": "266335040",
|
||||
"name": "webforai.kr",
|
||||
"publicId": "GTM-WQNTZVZH",
|
||||
"usageContext": [
|
||||
"WEB"
|
||||
]
|
||||
},
|
||||
"tag": [
|
||||
{
|
||||
"accountId": "6381018301",
|
||||
"containerId": "266335040",
|
||||
"tagId": "1",
|
||||
"name": "GA4 - 기본 태그",
|
||||
"type": "googtag",
|
||||
"parameter": [
|
||||
{
|
||||
"type": "TEMPLATE",
|
||||
"key": "tagId",
|
||||
"value": "G-013Z3BFY35"
|
||||
}
|
||||
],
|
||||
"firingTriggerId": [
|
||||
"2147479553"
|
||||
],
|
||||
"tagFiringOption": "ONCE_PER_EVENT"
|
||||
},
|
||||
{
|
||||
"accountId": "6381018301",
|
||||
"containerId": "266335040",
|
||||
"tagId": "2",
|
||||
"name": "GA4 이벤트 - generate_lead",
|
||||
"type": "gaawe",
|
||||
"parameter": [
|
||||
{
|
||||
"type": "TEMPLATE",
|
||||
"key": "eventName",
|
||||
"value": "generate_lead"
|
||||
},
|
||||
{
|
||||
"type": "TEMPLATE",
|
||||
"key": "measurementIdOverride",
|
||||
"value": "G-013Z3BFY35"
|
||||
},
|
||||
{
|
||||
"type": "LIST",
|
||||
"key": "eventSettingsTable",
|
||||
"list": [
|
||||
{
|
||||
"type": "MAP",
|
||||
"map": [
|
||||
{
|
||||
"type": "TEMPLATE",
|
||||
"key": "parameter",
|
||||
"value": "lead_source"
|
||||
},
|
||||
{
|
||||
"type": "TEMPLATE",
|
||||
"key": "parameterValue",
|
||||
"value": "{{DLV - lead_source}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"firingTriggerId": [
|
||||
"10"
|
||||
],
|
||||
"tagFiringOption": "ONCE_PER_EVENT"
|
||||
},
|
||||
{
|
||||
"accountId": "6381018301",
|
||||
"containerId": "266335040",
|
||||
"tagId": "3",
|
||||
"name": "GA4 이벤트 - click_tel",
|
||||
"type": "gaawe",
|
||||
"parameter": [
|
||||
{
|
||||
"type": "TEMPLATE",
|
||||
"key": "eventName",
|
||||
"value": "click_tel"
|
||||
},
|
||||
{
|
||||
"type": "TEMPLATE",
|
||||
"key": "measurementIdOverride",
|
||||
"value": "G-013Z3BFY35"
|
||||
}
|
||||
],
|
||||
"firingTriggerId": [
|
||||
"11"
|
||||
],
|
||||
"tagFiringOption": "ONCE_PER_EVENT"
|
||||
},
|
||||
{
|
||||
"accountId": "6381018301",
|
||||
"containerId": "266335040",
|
||||
"tagId": "4",
|
||||
"name": "GA4 이벤트 - click_email",
|
||||
"type": "gaawe",
|
||||
"parameter": [
|
||||
{
|
||||
"type": "TEMPLATE",
|
||||
"key": "eventName",
|
||||
"value": "click_email"
|
||||
},
|
||||
{
|
||||
"type": "TEMPLATE",
|
||||
"key": "measurementIdOverride",
|
||||
"value": "G-013Z3BFY35"
|
||||
}
|
||||
],
|
||||
"firingTriggerId": [
|
||||
"12"
|
||||
],
|
||||
"tagFiringOption": "ONCE_PER_EVENT"
|
||||
}
|
||||
],
|
||||
"trigger": [
|
||||
{
|
||||
"accountId": "6381018301",
|
||||
"containerId": "266335040",
|
||||
"triggerId": "10",
|
||||
"name": "CE - generate_lead",
|
||||
"type": "CUSTOM_EVENT",
|
||||
"customEventFilter": [
|
||||
{
|
||||
"type": "EQUALS",
|
||||
"parameter": [
|
||||
{
|
||||
"type": "TEMPLATE",
|
||||
"key": "arg0",
|
||||
"value": "{{_event}}"
|
||||
},
|
||||
{
|
||||
"type": "TEMPLATE",
|
||||
"key": "arg1",
|
||||
"value": "generate_lead"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"accountId": "6381018301",
|
||||
"containerId": "266335040",
|
||||
"triggerId": "11",
|
||||
"name": "링크 클릭 - tel",
|
||||
"type": "LINK_CLICK",
|
||||
"filter": [
|
||||
{
|
||||
"type": "STARTS_WITH",
|
||||
"parameter": [
|
||||
{
|
||||
"type": "TEMPLATE",
|
||||
"key": "arg0",
|
||||
"value": "{{Click URL}}"
|
||||
},
|
||||
{
|
||||
"type": "TEMPLATE",
|
||||
"key": "arg1",
|
||||
"value": "tel:"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"waitForTags": {
|
||||
"type": "BOOLEAN",
|
||||
"key": "waitForTags",
|
||||
"value": "false"
|
||||
},
|
||||
"checkValidation": {
|
||||
"type": "BOOLEAN",
|
||||
"key": "checkValidation",
|
||||
"value": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
"accountId": "6381018301",
|
||||
"containerId": "266335040",
|
||||
"triggerId": "12",
|
||||
"name": "링크 클릭 - mailto",
|
||||
"type": "LINK_CLICK",
|
||||
"filter": [
|
||||
{
|
||||
"type": "STARTS_WITH",
|
||||
"parameter": [
|
||||
{
|
||||
"type": "TEMPLATE",
|
||||
"key": "arg0",
|
||||
"value": "{{Click URL}}"
|
||||
},
|
||||
{
|
||||
"type": "TEMPLATE",
|
||||
"key": "arg1",
|
||||
"value": "mailto:"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"waitForTags": {
|
||||
"type": "BOOLEAN",
|
||||
"key": "waitForTags",
|
||||
"value": "false"
|
||||
},
|
||||
"checkValidation": {
|
||||
"type": "BOOLEAN",
|
||||
"key": "checkValidation",
|
||||
"value": "false"
|
||||
}
|
||||
}
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"accountId": "6381018301",
|
||||
"containerId": "266335040",
|
||||
"variableId": "20",
|
||||
"name": "DLV - lead_source",
|
||||
"type": "v",
|
||||
"parameter": [
|
||||
{
|
||||
"type": "INTEGER",
|
||||
"key": "dataLayerVersion",
|
||||
"value": "2"
|
||||
},
|
||||
{
|
||||
"type": "BOOLEAN",
|
||||
"key": "setDefaultValue",
|
||||
"value": "false"
|
||||
},
|
||||
{
|
||||
"type": "TEMPLATE",
|
||||
"key": "name",
|
||||
"value": "lead_source"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"builtInVariable": [
|
||||
{
|
||||
"accountId": "6381018301",
|
||||
"containerId": "266335040",
|
||||
"type": "CLICK_URL",
|
||||
"name": "Click URL"
|
||||
},
|
||||
{
|
||||
"accountId": "6381018301",
|
||||
"containerId": "266335040",
|
||||
"type": "PAGE_PATH",
|
||||
"name": "Page Path"
|
||||
},
|
||||
{
|
||||
"accountId": "6381018301",
|
||||
"containerId": "266335040",
|
||||
"type": "EVENT",
|
||||
"name": "Event"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -12,6 +12,7 @@ import { createPortal } from 'react-dom';
|
||||
import { AnimatePresence, motion } from 'motion/react';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { submitLead } from '../lib/submitLead';
|
||||
import { trackEvent } from '../lib/analytics';
|
||||
import { CONTACT_EMAIL } from '../lib/contact';
|
||||
|
||||
interface ContactModalProps {
|
||||
@ -86,6 +87,8 @@ export default function ContactModal({ open, onClose, source }: ContactModalProp
|
||||
source,
|
||||
website,
|
||||
});
|
||||
// 접수에 성공한 경우만 리드로 센다. 어느 버튼에서 열었는지(source)만 보낸다.
|
||||
if (result.ok) trackEvent('generate_lead', { lead_source: source });
|
||||
setState(result.ok ? 'done' : 'error');
|
||||
};
|
||||
|
||||
|
||||
11
src/lib/analytics.ts
Normal file
11
src/lib/analytics.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 방문 측정 이벤트. GTM 이 있는 배포(webforai.kr)에서만 dataLayer 로 넘기고, 없으면 아무것도 하지 않는다.
|
||||
* 이벤트에는 개인정보(이름·연락처·문의 내용)를 넣지 않는다. 어느 버튼에서 왔는지 같은 화면 정보만 보낸다.
|
||||
*/
|
||||
type DataLayerWindow = Window & { dataLayer?: Record<string, unknown>[] };
|
||||
|
||||
export function trackEvent(event: string, params: Record<string, string> = {}): void {
|
||||
const w = window as DataLayerWindow;
|
||||
if (!Array.isArray(w.dataLayer)) return;
|
||||
w.dataLayer.push({ event, ...params });
|
||||
}
|
||||
@ -1,7 +1,8 @@
|
||||
/**
|
||||
* 개인정보처리방침 (/privacy).
|
||||
* 이 사이트가 실제로 처리하는 것만 적는다: 문의·진단 요청 서식(이름·연락처·회사·문의 내용) → /api/lead(Vercel) →
|
||||
* Google Apps Script → 구글 시트 저장 + 알림 메일. 방문 분석 쿠키는 아직 쓰지 않는다. GA4 등을 붙이면 제8조를 고친다.
|
||||
* Google Apps Script → 구글 시트 저장 + 알림 메일. 방문 분석은 GTM(GTM-WQNTZVZH) 안의 GA4(G-013Z3BFY35)로 한다.
|
||||
* 광고 픽셀(Meta·네이버 등)을 GTM 에 추가하면 제1·5·8조를 함께 고친다.
|
||||
* 구조는 의료 분야 SaaS 방침(메디팔·애프터닥)의 조항 구성을 참고했고, 문장은 이 서비스에 맞춰 새로 썼다.
|
||||
*/
|
||||
import LegalLayout, { type LegalSection } from '../components/LegalLayout';
|
||||
@ -17,6 +18,7 @@ const SECTIONS: LegalSection[] = [
|
||||
'- 필수: 성함, 연락처(전화번호 또는 이메일 중 하나 이상)',
|
||||
'- 선택: 회사(병원)명, 문의 내용',
|
||||
'서비스 이용 과정에서 접속 IP 주소, 브라우저 정보, 접속 일시가 웹 호스팅 서버의 로그로 자동 생성될 수 있습니다.',
|
||||
'방문 분석을 위해 쿠키를 통해 방문 페이지, 체류 시간, 유입 경로, 기기·브라우저 종류, 대략적인 접속 지역(도시 단위)이 수집됩니다. 이 정보로 개인을 직접 식별하지 않습니다.',
|
||||
'회사는 만 14세 미만 아동의 개인정보를 수집하지 않으며, 환자의 건강 정보 등 민감정보를 서식으로 받지 않습니다. 문의 내용에 환자 개인정보를 적지 말아 주십시오.',
|
||||
],
|
||||
},
|
||||
@ -49,6 +51,7 @@ const SECTIONS: LegalSection[] = [
|
||||
body: [
|
||||
'회사는 서비스 운영을 위해 아래와 같이 개인정보 처리업무를 위탁하며, 수탁자의 서버가 국외에 있어 개인정보가 국외로 이전됩니다.',
|
||||
'- Google LLC (미국): 문의 내용 저장(Google 스프레드시트) 및 접수 알림 메일 발송. 이전 항목은 제1조의 서식 입력 항목이며, 서식을 제출하는 시점에 네트워크로 전송됩니다. 보유 기간은 제3조와 같습니다.',
|
||||
'- Google LLC (미국): 웹사이트 방문 분석(Google 애널리틱스, Google 태그 관리자). 이전 항목은 제1조의 방문 분석 정보이며, 웹사이트를 이용하는 시점에 네트워크로 전송됩니다. 보유 기간은 수집일부터 최대 14개월입니다.',
|
||||
'- Vercel Inc. (미국): 웹사이트 호스팅 및 서식 전송 처리. 이전 항목은 서식 입력 항목과 접속 기록이며, 웹사이트 이용 및 서식 제출 시 네트워크로 전송됩니다. 보유 기간은 위탁 계약 종료 시까지입니다.',
|
||||
'국외 이전을 원하지 않으시면 서식 대신 이메일 또는 전화로 문의하실 수 있습니다. 다만 이 경우에도 회신 메일은 회사의 메일 서비스를 통해 처리됩니다.',
|
||||
],
|
||||
@ -72,8 +75,12 @@ const SECTIONS: LegalSection[] = [
|
||||
{
|
||||
title: '쿠키 등 자동 수집 장치',
|
||||
body: [
|
||||
'회사는 현재 이 웹사이트에서 광고 또는 방문 분석을 위한 쿠키를 사용하지 않습니다.',
|
||||
'방문 분석 도구를 도입하는 경우 이 방침을 개정해 수집 항목, 목적, 거부 방법을 미리 알려 드립니다.',
|
||||
'회사는 웹사이트 이용 현황을 파악하고 서비스를 개선하기 위해 Google 애널리틱스의 분석 쿠키를 사용합니다. 광고 목적의 쿠키는 사용하지 않습니다.',
|
||||
'- 수집 항목: 제1조의 방문 분석 정보',
|
||||
'- 이용 목적: 방문자 수, 많이 본 페이지, 유입 경로, 문의 전환 등 이용 현황 분석',
|
||||
'- 거부 방법: 브라우저의 개인정보 보호 또는 쿠키 및 사이트 데이터 설정에서 이 사이트(webforai.kr)의 쿠키를 차단하거나 삭제할 수 있습니다. Google 애널리틱스 차단 브라우저 부가기능(tools.google.com/dlpage/gaoptout)으로도 수집을 거부할 수 있습니다.',
|
||||
'쿠키 저장을 거부해도 웹사이트 이용과 문의에는 제한이 없습니다.',
|
||||
'광고 목적의 쿠키나 추적 도구를 도입하는 경우 이 방침을 개정해 미리 알려 드립니다.',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@ -41,6 +41,15 @@ export default defineConfig(({mode}) => {
|
||||
'<link rel="apple-touch-icon" href="/discovery-apple-touch-icon.png" />',
|
||||
]
|
||||
: [];
|
||||
// 방문 측정: Google 태그 관리자(GTM) 컨테이너. GA4(G-013Z3BFY35)와 키 이벤트는 컨테이너 안에서 설정한다.
|
||||
// 공개 배포(webforai.kr)에만 넣는다. 컨테이너 ID 는 공개 값이다. 개인정보처리방침 제5·8조와 함께 관리한다.
|
||||
const GTM_ID = 'GTM-WQNTZVZH';
|
||||
const gtmHead = isDiscovery
|
||||
? [`<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','${GTM_ID}');</script>`]
|
||||
: [];
|
||||
const gtmBody = isDiscovery
|
||||
? `<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=${GTM_ID}" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>`
|
||||
: '';
|
||||
const headHtml = [
|
||||
`<title>${head.title}</title>`,
|
||||
`<meta name="description" content="${head.description}" />`,
|
||||
@ -50,6 +59,7 @@ export default defineConfig(({mode}) => {
|
||||
`<meta property="og:locale" content="ko_KR" />`,
|
||||
...share,
|
||||
...verify,
|
||||
...gtmHead,
|
||||
].join('\n ');
|
||||
|
||||
return {
|
||||
@ -78,7 +88,9 @@ export default defineConfig(({mode}) => {
|
||||
{
|
||||
name: 'infinith-head',
|
||||
transformIndexHtml(html: string) {
|
||||
return html.replace(/<title>[\s\S]*?<\/title>/, headHtml);
|
||||
return html
|
||||
.replace(/<title>[\s\S]*?<\/title>/, headHtml)
|
||||
.replace('<body>', gtmBody ? `<body>\n ${gtmBody}` : '<body>');
|
||||
},
|
||||
},
|
||||
tailwindcss(),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user