"강남 정형외과", "부천 소아과" 같은 검색어는 이미 답이 정해진 질문입니다. 검색자는 질환을 공부하려는 게 아니라 지금 갈 수 있는 병원을 고르려는 상태이고, 그래서 이 검색어군은 홈페이지로 들어오는 유입 중 문의·예약까지 경로가 가장 짧습니다. 문제는 이 검색어가 하나가 아니라 대략 (노려볼 만한 지역 수 × 표시과목 수)만큼 존재한다는 점입니다.
여기서 원장님들이 보통 두 갈래로 갑니다. 하나는 홈페이지 메인 한 장으로 모든 지역 검색어를 받으려는 방식이고, 다른 하나는 지역 이름만 치환한 페이지를 수십 장 찍어내는 방식입니다. 앞의 것은 어느 검색어에도 명확히 대응하지 못해 검색엔진이 페이지 주제를 좁히지 못하고, 뒤의 것은 구글이 doorway page(관문 페이지)로 판단해 색인에서 빼거나 평가를 낮출 위험을 만듭니다. 의료는 검색엔진이 특히 보수적으로 다루는 주제군이라, 얇은 페이지를 대량 생산했을 때의 손해가 다른 업종보다 큽니다.
세 번째 길이 프로그래매틱 지역 페이지입니다. 템플릿은 공유하되 데이터는 공유하지 않는 구조로, 페이지 수를 늘리면서도 각 페이지가 고유한 사실을 갖게 만드는 방식입니다. 아래는 웨비스튜디오가 자사 사이트에서 실제로 쓰는 "검색의도 랜딩 팩토리" 구조를 병원·의원 지역 페이지에 옮길 때의 설계 기준과, 의료법 제56·57조 때문에 일반 업종과 달라져야 하는 지점을 정리한 것입니다.
지역 페이지를 만들지 말지부터 정한다
페이지를 늘리는 결정은 검색량이 아니라 "그 지역 전용으로 쓸 수 있는 사실이 있는가"로 판단하는 편이 안전합니다. 검색량은 있는데 쓸 사실이 없으면 그 페이지는 필연적으로 얇아집니다.
| 판단 질문 | 예 → 별도 페이지 | 아니오 → 만들지 않음 |
|---|---|---|
| 그 지역에 지점·분원이 실제로 있는가 | 지점 전용 페이지(주소·좌표·진료시간 고유) | 상위 지역 페이지에 통합 |
| 지점은 없지만 실제 내원 반경 안인가 | 접근 경로 중심 페이지(노선·소요시간) | 만들지 않음 |
| 그 지역 고유 사실을 3개 이상 쓸 수 있는가 | 생성 | 생성 보류 |
| 화면 문구가 옆 지역 페이지와 문장 단위로 다른가 | 생성 | 데이터부터 다시 |
여기서 "고유 사실 3개"는 실무 임계값으로 쓸 만합니다. 지하철 출구와 도보 시간, 버스 정류장명, 주차 조건, 해당 지점의 요일별 진료시간, 인근 랜드마크, 그 지역에서 자주 들어오는 문의 유형 중 최소 3개를 못 채우면 그 페이지는 만들지 않는 쪽이 총점에 유리합니다.
데이터 1개 + 템플릿 1개로 N개를 만들되, 고유하게
웨비스튜디오 자사 사이트의 검색의도 랜딩은 데이터 객체 하나(searchIntentPages)와 공통 React 템플릿 하나로 구성됩니다. 라우트마다 데이터 키를 넘기면 페이지가 생성되지만, 생성된 페이지는 서로 다음을 공유하지 않습니다.
- 고유 canonical —
createCanonicalAlternates('/' + data.slug)로 페이지마다 자기 slug 기준 대표 URL을 갖습니다. - 고유 JSON-LD —
@graph안의 모든 노드가 자기 URL을@id접두로 씁니다({url}#faq,{url}#clinic형태). 두 페이지의 스키마가 같은@id를 갖는 순간 검색엔진에게는 같은 개체가 됩니다. - 고유 FAQ 배열 — 페이지마다 FAQ 질문·답변이 다릅니다.
- 고유 검색어 목록(
focusTerms) — ItemList로 노출되며, 그 페이지가 어떤 질문에 답하는 페이지인지 기계가 읽을 수 있게 합니다.
병원 지역 페이지로 옮기면 공유해도 되는 것과 반드시 달라야 하는 것이 이렇게 갈립니다.
| 구성 요소 | 공유 가능 | 페이지마다 달라야 함 |
|---|---|---|
| 레이아웃·컴포넌트·CSS | O | - |
| 진료 절차 설명, 준비물 안내 | O(사실 기반이면) | - |
| title / description / H1 | X | 지역명 + 법정 표시과목 조합 |
| canonical | X | 자기 URL |
JSON-LD @id·주소·좌표·진료시간 | X | 지점 실제 값 |
| FAQ 4~6개 | X | 최소 절반은 그 지역 전용 질문 |
| 오시는길 본문 | X | 노선·출구·주차 실제 값 |
| 대표 이미지 alt | X | 지점명·지역명 반영 |
배포 전 검수 기준은 단순합니다. 두 지역 페이지를 나란히 놓고 지역명만 가렸을 때 사람이 구분하지 못하면, 검색엔진도 구분하지 않습니다. 그런 페이지는 sitemap에 넣기 전에 되돌립니다.
MedicalClinic 스키마: 무엇을 넣고 무엇을 일부러 빼는가
지역 페이지의 구조화 데이터는 MedicalClinic(상위가 필요하면 MedicalOrganization)을 중심으로, 주소는 PostalAddress, 좌표는 GeoCoordinates, 진료시간은 OpeningHoursSpecification으로 씁니다.
{
"@context": "https://schema.org",
"@type": "MedicalClinic",
"@id": "https://example-clinic.co.kr/bucheon/pediatrics#clinic",
"name": "○○의원",
"url": "https://example-clinic.co.kr/bucheon/pediatrics",
"telephone": "+82-32-000-0000",
"medicalSpecialty": "Pediatric",
"address": {
"@type": "PostalAddress",
"streetAddress": "○○로 00, 3층",
"addressLocality": "부천시",
"addressRegion": "경기도",
"postalCode": "14000",
"addressCountry": "KR"
},
"geo": { "@type": "GeoCoordinates", "latitude": 37.5035, "longitude": 126.7660 },
"openingHoursSpecification": [
{ "@type": "OpeningHoursSpecification",
"dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
"opens": "09:00", "closes": "18:30" },
{ "@type": "OpeningHoursSpecification",
"dayOfWeek": "Saturday", "opens": "09:00", "closes": "13:00" }
],
"areaServed": [{ "@type": "City", "name": "부천시" }],
"hasMap": "https://map.naver.com/...",
"sameAs": ["https://map.naver.com/...", "https://www.google.com/maps/..."]
}
필드별 판단 기준은 이렇습니다.
| 필드 | 넣는 기준 | 흔한 실수 |
|---|---|---|
@id | 페이지 URL 기준 고유 앵커 | 전 지점이 같은 @id |
medicalSpecialty | schema.org의 열거값 사용 | 한글 과목명을 그대로 넣어 무시됨 |
geo | 지점 실좌표 소수점 4자리 이상 | 지역 중심 좌표를 돌려씀 |
openingHoursSpecification | 요일별 분리, 점심·휴진 반영 | 전 지점 동일 시간 하드코딩 |
areaServed | 실제 진료권 도시·구 | 전국 나열 |
sameAs | 스마트플레이스·구글 지도 등 검증 가능한 프로필 | SNS만 나열 |
review / aggregateRating | 넣지 않음 | 별점 리치 결과 욕심에 삽입 |
마지막 줄이 의료 분야의 결정적 차이입니다. 일반 업종에서는 aggregateRating이 클릭률 자산이지만, 의료기관 홈페이지에서 환자 후기·평점을 싣는 것은 의료법 제56조 제2항이 금지하는 치료경험담 광고에 해당할 소지가 큽니다. 구조화 데이터는 화면에 있는 내용을 기계용으로 요약한 것이므로, 화면에 실을 수 없는 것은 스키마에도 실을 수 없습니다.
출력 방식도 정해 둡니다. 모든 JSON-LD는 serializeJsonLd로 직렬화해 < 를 \u003c 로 이스케이프한 뒤 <script type="application/ld+json">에 넣습니다. 병원 페이지는 지점명·주소·의료진 표기처럼 운영자가 입력한 값이 그대로 스키마에 들어가기 때문에, 이 한 줄이 스크립트 조기 종료와 주입을 막는 방어선 역할을 합니다.
진료과목은 검색어가 아니라 법정 명칭으로 쓴다
사람들이 검색하는 말과 의료기관이 표시할 수 있는 진료과목 명칭은 다릅니다. 표기는 법정 표시과목을 따르고, 검색어 대응은 본문 문장과 FAQ의 자연어에서 처리하는 것이 안전한 분리입니다.
| 검색어에서 흔한 표현 | 표기·간판·스키마의 표시과목 |
|---|---|
| 소아과 | 소아청소년과 |
| 비뇨기과 | 비뇨의학과 |
| 정신과 | 정신건강의학과 |
| 통증의학과 | 마취통증의학과 |
| 성형·피부 통칭 | 성형외과 / 피부과(개설 과목대로) |
즉 URL과 H1에는 "부천 소아청소년과"를 쓰고, 본문에는 "부천에서 소아과를 찾으시는 분들이 자주 묻는 내용" 같은 문장으로 검색어를 자연스럽게 포함시킵니다. 표시과목을 검색어에 맞춰 바꾸는 순간 표기 위반이 N개 페이지로 복제됩니다.
'전문'과 '특화'도 같은 원리입니다. 보건복지부 지정 전문병원이거나 법정 전문과목 전문의 자격이라는 근거가 있을 때만 쓰고, 없으면 '중점진료'로 표기합니다. 근거 없이 "○○ 전문 클리닉"이라고 쓰는 대신 "정형외과 전문의 진료", "척추·관절 중점진료"처럼 검증 가능한 사실로 바꿉니다.
오시는길·진료시간·주차: 로컬 신호는 HTML에도 있어야 한다
구조화 데이터만 채우고 화면에는 지도 이미지 한 장만 있는 페이지가 의외로 많습니다. 사용자에게 보이지 않는 내용을 마크업하는 것은 구조화 데이터 정책 위반이고, 실무적으로도 사람이 못 읽는 정보는 전환에 기여하지 않습니다.
- 주소 전체(도로명 + 건물명 + 층/호)를 선택 가능한 텍스트로 노출. 이미지 안에만 넣지 않기
- 전화번호는
tel:링크로. 모바일 검색 유입의 상당수가 통화 의도입니다 - 요일별 진료시간 표에 점심시간, 야간진료 요일, 휴진일, 공휴일 정책을 분리 표기
- 주차 조건(건물 주차 가능 여부, 대수, 무료/유료, 제휴 주차장)을 사실대로. 무료 주차는 편의시설 사실이므로 표기 가능하지만, "이달 주차비 지원" 같은 형태로 쓰면 경제적 이익 제공 광고로 읽힐 수 있어 피합니다
- 대중교통은 호선·출구 번호·도보 분, 버스 정류장 명칭까지
- 지도는 네이버 지도·카카오맵 링크와 함께, 정적 이미지에는 지점명이 들어간 alt
- NAP(상호·주소·전화)를 홈페이지, 네이버 스마트플레이스, 구글 비즈니스 프로필에서 문자 단위로 통일. 층 표기 하나가 달라도 동일 개체 판정이 흔들립니다
판단 기준 하나만 기억하면 됩니다. 지점마다 진료시간이 다르면 지점 페이지마다 openingHoursSpecification을 따로 둡니다. 하나를 돌려쓰면 화면과 스키마가 어긋나고, 그 불일치는 검색엔진과 방문자 양쪽에서 동시에 손해입니다.
구글과 네이버는 같은 페이지를 다르게 본다
| 항목 | 구글 | 네이버 |
|---|---|---|
| 지역 결과의 1차 자산 | 구글 비즈니스 프로필 | 네이버 스마트플레이스 |
| 홈페이지 JSON-LD | 개체 이해·리치 결과에 직접 활용 | 수집은 되나 플레이스 DB가 우선, 보조 신호 성격 |
| 등록·점검 도구 | Search Console | 네이버 서치어드바이저 |
| sitemap | 자동 발견 비중 큼 | 서치어드바이저에 직접 제출하는 편이 확실 |
| 업종 분류 | 프로필 카테고리 | 플레이스 업종 분류 |
| 결과 표면 | 웹 결과 + 지도 팩 | 플레이스·블로그·카페·웹사이트 탭이 분리 |
| 홈페이지의 역할 | 순위 자산 그 자체 | 플레이스 정보의 사실 대조군 |
정리하면, 지역 페이지는 구글에서 직접 순위를 겨루는 자산이고 네이버에서는 플레이스 정보가 실재하고 일관됨을 뒷받침하는 근거 자료에 가깝습니다. 그래서 네이버 중심이라도 지역 페이지를 만들 이유는 충분하지만, 네이버 쪽 개선의 첫 수는 홈페이지가 아니라 스마트플레이스의 업종·주소·진료시간·대표 이미지 정합성입니다. 순서를 뒤집으면 노력 대비 반응이 잘 안 옵니다.
리뷰 지표를 KPI로 잡는 설계는 권하지 않습니다. 환자가 자발적으로 남긴 플랫폼 리뷰 자체와 별개로, 의료기관이 대가나 유인을 제공해 후기를 작성하게 하면 치료경험담 광고 문제로 이어질 수 있고, 그 후기를 홈페이지로 옮겨 싣는 순간에는 명백히 의료기관의 광고물이 됩니다.
의료법이 템플릿 구조 자체를 바꾼다
자체 홈페이지는 의료광고 사전심의 대상 매체가 아니라는 전제로 설계하되, 제56조의 금지 유형은 매체와 무관하게 그대로 적용되고 자율심의기구의 사후 모니터링 대상이 된다는 점을 함께 전제합니다. 프로그래매틱에서 특히 중요한 이유는 산술적입니다. 수동 페이지에서 문구 하나를 잘못 쓰면 위반은 1건이지만, 템플릿에서 잘못 쓰면 지역 수만큼 복제됩니다.
| 금지 유형 | 템플릿에 스며들기 쉬운 형태 | 대체 |
|---|---|---|
| 치료효과 단정·보장 | "확실한 개선", "재발 없는", "완치" | 검사·시술의 절차, 대상, 소요 시간, 준비사항 |
| 최상급·비교우위 | "강남 최고", "1위", "최다", "유일한" | 개원 연도, 보유 장비 명칭, 진료 요일 |
| 치료경험담·후기 | 환자 후기 슬라이더, 실명·이니셜 사례 | 삭제(대체 요소를 두지 않음) |
| 시술 전후 비교 | before/after 갤러리 | 시설·장비 사진 |
| 비급여 할인·이벤트·경품 | "이달의 프로모션", "지역 주민 혜택" | 비급여 진료비용 고지만 사실대로 |
그래서 템플릿 레벨의 안전장치를 코드로 겁니다.
- 데이터 타입에 자유 서술 필드를 만들지 않는다. 지역 페이지 데이터 타입을
district,subwayExit,walkMinutes,parking,hours,specialty같은 사실 필드로만 구성하면,catchphrase같은 칸이 없으니 최상급 문구가 들어갈 자리도 없습니다. 스키마 설계가 곧 규제 준수 장치가 됩니다. - 표시과목을 유니온 타입으로 고정한다. 법정 표시과목만 값으로 허용해
'소아과'는 타입 에러가 나게 합니다. 사람이 검수하지 않아도 잘못된 표기가 빌드를 통과하지 못합니다. - 금지 어휘 검사를 빌드에 붙인다. 최상급·보장·할인·후기 계열 어휘 사전으로 데이터 파일과 렌더 결과 문자열을 검사하고, 걸리면 빌드를 실패시킵니다. Next.js 15 App Router의 standalone 빌드 파이프라인에 넣으면 배포 이전 단계에서 멈춥니다.
- FAQ 답변도 같은 검사를 통과시킨다. FAQPage 구조화 데이터는 생성형 AI 답변에 그대로 인용될 가능성이 있어, 여기에 들어간 한 문장은 검색 결과 밖에서도 재유통됩니다.
- 변경 이력을 남긴다. 사후 모니터링이나 민원 대응에서 "언제 무엇을 어떻게 고쳤는지"를 설명할 수 있어야 합니다.
검색·AI 노출은 클릭이나 계약을 보장하지 않습니다. 이 글의 설계는 검색엔진과 생성형 AI가 병원 정보를 정확히 읽고 설명할 가능성을 높이기 위한 것이며, 순위·문의 수·매출을 약속하지 않습니다. 개별 표현의 의료법 위반 여부는 최종적으로 관할 보건소와 자율심의기구의 판단을 따릅니다.
만든 페이지를 기계가 실제로 읽게 만드는 경로
페이지를 잘 만들어도 수집 경로가 없으면 존재하지 않는 것과 같습니다. 웨비스튜디오가 자사 사이트에서 운영하는 방식은 네 갈래입니다.
- 런타임
/sitemap.xml— 지역 페이지가 데이터에 추가되면 자동 반영됩니다. 정적 파일로 관리하면 페이지를 늘릴 때마다 갱신 누락이 생깁니다. /rss.xml— 콘텐츠 갱신 신호를 별도 채널로 노출합니다./llms.txt— 생성형 AI와 수집기를 위한 기계 판독용 카탈로그입니다. 지역·진료과목 페이지 목록과 한 줄 설명을 텍스트로 제공하되, 이 문구도 앞서 만든 금지 어휘 검사를 똑같이 통과시킵니다.- 내부 링크 구조 — 지역 허브에서 각 지역 상세로, 진료과목 허브에서 지역별 페이지로 양방향 연결해 고아 페이지를 없앱니다. 프로그래매틱 페이지가 색인되지 않는 원인 1순위는 링크가 아예 없다는 것입니다.
실행 순서 체크리스트
- 지점·진료권을 기준으로 만들 지역 목록을 확정하고, 고유 사실 3개를 못 채우는 지역은 목록에서 제외
- 표시과목을 법정 명칭 유니온 타입으로 정의
- 지역 데이터 객체를 사실 필드만으로 설계(자유 서술 칸 없음)
- 공통 템플릿 1개 작성, 페이지별 canonical·
@id·FAQ·오시는길을 데이터에서 주입 -
MedicalClinic+PostalAddress+GeoCoordinates+OpeningHoursSpecification구성,review/aggregateRating제외 - 모든 JSON-LD를
serializeJsonLd로 이스케이프해 출력 - 금지 어휘 검사를 빌드 단계에 연결하고 실패 시 배포 중단
- NAP를 홈페이지·스마트플레이스·구글 비즈니스 프로필에서 문자 단위로 일치
- 런타임 sitemap, rss, llms.txt에 신규 페이지 반영 확인
- 서치어드바이저·Search Console에서 수집·색인 상태를 페이지 단위로 확인
지역 페이지의 성패는 페이지 수가 아니라 페이지당 사실의 밀도에서 갈립니다. 만들 수 있는 페이지를 다 만드는 대신 근거를 채울 수 있는 페이지만 만들고, 그 근거를 화면과 구조화 데이터에 동일하게 싣고, 의료법 금지 표현이 들어갈 자리를 데이터 구조에서 미리 없애는 것. 이 세 가지가 자동 생성 방식을 안전하게 만드는 조건입니다.