마케팅 · 자동화
학원 키워드 마케팅 자동화
원장 설문에서 시작해 지역·키워드 수집, AI 포지셔닝, 콘텐츠 생성·검수, 승인, 발행까지 한 줄로 이어지는 파이프라인. 학원마다 자기 도메인을 갖는 멀티테넌트 구조.
- 기간
- 2026.07 —
- 역할
- 기획·설계·구현 단독
- 상태
- 프로토타입
- 규모
- 4,050줄
문제
학원 마케팅은 키워드를 찾는 일과 글을 쓰는 일이 따로 논다. 원장이 직접 하기엔 품이 들고, 대행을 맡기면 현장을 모르는 글이 나온다.
해결
설문에서 얻은 현장 정보를 키워드 조회량·반경 경쟁 데이터와 묶어 포지셔닝을 먼저 잡고, 그 위에서 콘텐츠를 생성한다. 생성형 엔진이 읽을 수 있도록 llms.txt와 구조화 피드를 함께 뱉는다.
멀티테넌트
학원별 개별 사이트
blog · faq · feed.xml · llms.txt
2km
반경 경쟁 분석
문제
학원 마케팅은 두 가지 일이 따로 놉니다.
- 무슨 키워드로 쓸지 정하는 일 — 조회량과 경쟁 상황을 봐야 하는데, 원장이 직접 하기엔 품이 듭니다
- 그 키워드로 글을 쓰는 일 — 대행을 맡기면 현장을 모르는 글이 나옵니다
그래서 대부분 “원장이 직접 쓰다 지쳐서 멈추거나, 대행사 글이 학원과 안 닮거나” 둘 중 하나로 끝납니다.
여기에 최근 변수가 하나 더 붙었습니다. 학부모가 검색창이 아니라 AI에게 묻기 시작했습니다. 사람이 읽을 글과 생성 엔진이 읽을 구조가 둘 다 필요해졌습니다.
설계 결정
| 결정 | 이유 |
|---|---|
| 설문을 파이프라인의 입구로 | 현장 정보(강점·수업 방식·타깃 학년)가 없으면 어떤 AI를 써도 일반론이 나온다. 사람만 아는 것을 먼저 받는다 |
| 포지셔닝을 콘텐츠보다 먼저 | 글을 먼저 쓰면 매번 톤이 달라진다. 포지셔닝을 고정하고 그 위에서 생성한다 |
학원마다 자기 사이트 (/[slug]) |
학원은 남의 플랫폼 안 페이지가 아니라 자기 주소를 원한다. 멀티테넌트로 처음부터 설계 |
| 네이버는 반자동 | 공식 발행 API가 없다. 로컬 Playwright 헬퍼가 본문까지 채우고, 캡차와 최종 발행 버튼은 사람이 누른다 |
| AEO를 나중이 아니라 처음부터 | llms.txt·JSON-LD·요약 박스는 나중에 붙이면 전 페이지를 다시 손봐야 한다 |
파이프라인
원장 설문
↓
지역·키워드 수집 네이버 검색광고 API (조회량)
↓ 카카오 로컬 API (반경 2km 학교·경쟁 학원)
AI 포지셔닝 Claude
↓
콘텐츠 생성 → 검수 → 승인
↓
발행 ├ 자체 페이지 /[slug], /[slug]/blog, /[slug]/faq (완전 자동)
├ 네이버 로컬 Playwright 헬퍼 (반자동)
└ 메타 Graph API (후순위)
Next.js 15 App Router · Supabase(Postgres + Auth + RLS) · 60개 파일 약 4,050줄.
생성 엔진까지 읽히게
사람이 읽을 글과 AI가 읽을 구조를 같은 파이프라인에서 냅니다.
- 답변을 먼저 쓰는 문체와
#tldr요약 박스 Article/FAQPage/EducationalOrganizationJSON-LD- 블로그에는 speakable 마크업
- 학원별
llms.txt와feed.xml - 지오코딩한 좌표를
GeoCoordinates로
어려웠던 문제
멀티테넌트에서 제일 조용한 사고
블로그 목록과 FAQ 페이지에 generateMetadata가 없었습니다. 그래서 두 페이지가 루트 타이틀을 상속했고, 결과적으로 학원 공개 페이지의 <title>에 툴 이름이 그대로 노출·색인됐습니다.
에러도 안 나고 화면도 멀쩡합니다. 검색 결과에서만 티가 납니다. 멀티테넌트에서는 “테넌트 것이어야 할 값이 플랫폼 것으로 새는” 사고가 가장 조용히 일어납니다. canonical도 같이 빠져 있었습니다.
이후로 테넌트 라우트를 추가할 때 메타데이터와 canonical을 체크리스트에 넣었습니다.
공식 API가 없는 채널
네이버 블로그·플레이스는 발행 API가 없습니다. 선택지는 셋이었습니다.
- 자동화를 포기한다
- 전 과정을 자동화한다 — 캡차를 뚫어야 하고, 약관을 벗어납니다
- 사람이 눌러야 하는 지점만 사람에게 남긴다
3번으로 갔습니다. 로컬 헬퍼가 제목·본문·태그까지 채워 놓고 멈춥니다. 캡차와 최종 발행은 사람이 합니다. 반복 노동은 사라지고 책임 지점은 사람에게 남습니다.
모델이 바뀌면 파라미터도 바뀐다
Claude 래퍼에서 temperature를 넘기다 최신 모델에서 실패했습니다. 지원하지 않는 파라미터였습니다. 래퍼 한 곳(lib/ai/claude.ts)에 모아 뒀던 덕에 한 파일만 고쳤고, 같은 실수를 반복하지 않도록 그 자리에 이유를 주석으로 남겼습니다.
배운 것
- AI 결과물의 품질은 프롬프트보다 입력에서 갈립니다. 설문으로 받은 현장 정보 몇 줄이 프롬프트 몇 페이지보다 큽니다.
- 자동화의 경계는 기술이 아니라 책임으로 긋습니다. 캡차는 뚫을 수 있느냐가 아니라 뚫어도 되느냐의 문제입니다.
- 멀티테넌트의 버그는 조용합니다. 화면이 멀쩡한데 값이 새는 경로를 따로 점검해야 합니다.
English Summary
A multi-tenant marketing pipeline for cram schools: an owner survey feeds local keyword collection, AI positioning, content generation, review, approval, and publishing. Each school gets its own routes (/[slug], /blog, /faq) with per-tenant metadata, JSON-LD, llms.txt, and feeds — built for answer engines as well as search.
Stack: Next.js 15 App Router + TypeScript + Tailwind, Supabase (Postgres, Auth, RLS), Claude API, Naver Search Ad API for search volume, Kakao Local API for a 2km competitive radius, local Playwright helper for semi-automated Naver publishing. 60 files, ~4k LOC.
Notable problems solved: a silent multi-tenant leak where tenant pages inherited the platform’s <title> into search results, and drawing the automation boundary at human responsibility (CAPTCHA and final publish stay manual) rather than at technical feasibility.