방식 개편
This commit is contained in:
+32
-22
@@ -17,10 +17,12 @@ export interface ParsedRouteParams {
|
||||
dong: string
|
||||
shortRegionName: string
|
||||
grade: string
|
||||
gradeShort: string
|
||||
subjectKey: string
|
||||
subjectName: string
|
||||
targetTerm: string
|
||||
fullKeyword: string
|
||||
isRegionOnly?: boolean
|
||||
}
|
||||
|
||||
// 5대 핵심 과목 정의
|
||||
@@ -34,30 +36,34 @@ export const SUBJECTS_MAP: Record<string, { key: string, name: string, badge: st
|
||||
|
||||
// 학년 정의
|
||||
export const GRADES_LIST = [
|
||||
{ key: 'middle', name: '중학생', aliases: ['중학생', '중1', '중2', '중3', '중등'] },
|
||||
{ key: 'high', name: '고등학생', aliases: ['고등학생', '고1', '고2', '고3', '고등', '예비고1'] },
|
||||
{ key: 'elem', name: '초등학생', aliases: ['초등학생', '초등', '초5', '초6'] }
|
||||
{ key: 'middle', name: '중학생', shortName: '중등', aliases: ['중학생', '중1', '중2', '중3', '중등'] },
|
||||
{ key: 'high', name: '고등학생', shortName: '고등', aliases: ['고등학생', '고1', '고2', '고3', '고등', '예비고1', 'N수', '재수'] },
|
||||
{ key: 'elem', name: '초등학생', shortName: '초등', aliases: ['초등학생', '초등', '초1', '초2', '초3', '초4', '초5', '초6'] }
|
||||
]
|
||||
|
||||
// 동 이름으로 지역 검색
|
||||
export function getRegionByDong(dongQuery: string): RegionInfo | null {
|
||||
if (!dongQuery) return null
|
||||
const clean = dongQuery.trim().replace(/동$/, '') // '황금' 또는 '황금동' 모두 매칭
|
||||
const clean = dongQuery.trim().replace(/[동읍면리가]$/, '')
|
||||
const map = regionsData.dongMap as Record<string, RegionInfo[]>
|
||||
|
||||
// 1. 정확 매칭 (황금동)
|
||||
// 1. 정확 매칭
|
||||
if (map[dongQuery] && map[dongQuery].length > 0) {
|
||||
return map[dongQuery][0]
|
||||
}
|
||||
// 2. '동' 붙여서 매칭
|
||||
if (map[dongQuery + '동'] && map[dongQuery + '동'].length > 0) {
|
||||
return map[dongQuery + '동'][0]
|
||||
// 2. '동', '읍', '면' 붙여서 매칭
|
||||
for (const suffix of ['동', '읍', '면']) {
|
||||
if (map[clean + suffix] && map[clean + suffix].length > 0) {
|
||||
return map[clean + suffix][0]
|
||||
}
|
||||
}
|
||||
// 3. 리스트 전체 순회 매칭
|
||||
const found = (regionsData.list as RegionInfo[]).find(r =>
|
||||
r.dong === dongQuery ||
|
||||
r.dong === (dongQuery + '동') ||
|
||||
r.dong.startsWith(clean)
|
||||
r.dong === (clean + '동') ||
|
||||
r.dong === (clean + '읍') ||
|
||||
r.dong === (clean + '면') ||
|
||||
(clean.length >= 2 && r.dong.startsWith(clean))
|
||||
)
|
||||
return found || null
|
||||
}
|
||||
@@ -99,12 +105,6 @@ export function getShortRegionName(sido: string, dong: string, sigungu?: string)
|
||||
}
|
||||
|
||||
// 지능형 URL 슬러그 파서
|
||||
// 지원 예시:
|
||||
// - ['대구광역시', '수성구', '황금동']
|
||||
// - ['대구광역시', '황금동', '중학생', '영어']
|
||||
// - ['황금동', '중학생', '영어']
|
||||
// - ['황금동-중학생-영어']
|
||||
// - '대구광역시-황금동-중학생-영어'
|
||||
export function parseRouteSlug(slugInput: string | string[]): ParsedRouteParams {
|
||||
let tokens: string[] = []
|
||||
|
||||
@@ -117,6 +117,7 @@ export function parseRouteSlug(slugInput: string | string[]): ParsedRouteParams
|
||||
|
||||
let matchedRegion: RegionInfo | null = null
|
||||
let detectedGrade = '중학생'
|
||||
let detectedGradeShort = '중등'
|
||||
let detectedSubjectKey = 'english'
|
||||
let detectedSubjectName = '영어'
|
||||
let detectedTerm = '과외'
|
||||
@@ -141,30 +142,37 @@ export function parseRouteSlug(slugInput: string | string[]): ParsedRouteParams
|
||||
}
|
||||
}
|
||||
|
||||
// 기본값 (568개 타겟 지역 중 첫 번째 기준)
|
||||
// 기본값 (타겟 지역 중 첫 번째 기준)
|
||||
if (!matchedRegion) {
|
||||
matchedRegion = (regionsData.list as RegionInfo[])[0] || {
|
||||
fullName: '경기도 고양시 덕양구 고양동',
|
||||
sido: '경기도',
|
||||
sigungu: '고양시 덕양구',
|
||||
dong: '고양동'
|
||||
fullName: '울산광역시 남구 옥동',
|
||||
sido: '울산광역시',
|
||||
sigungu: '남구',
|
||||
dong: '옥동'
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 학년 추출
|
||||
let hasExplicitGrade = false
|
||||
const fullText = tokens.join(' ')
|
||||
for (const g of GRADES_LIST) {
|
||||
if (g.aliases.some(a => fullText.includes(a))) {
|
||||
detectedGrade = g.name
|
||||
detectedGradeShort = g.shortName
|
||||
hasExplicitGrade = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const gradeShort = detectedGradeShort || (detectedGrade === '초등학생' ? '초등' : detectedGrade === '고등학생' ? '고등' : '중등')
|
||||
|
||||
// 4. 과목 추출
|
||||
let hasExplicitSubject = false
|
||||
for (const [key, sub] of Object.entries(SUBJECTS_MAP)) {
|
||||
if (sub.aliases.some(a => fullText.includes(a))) {
|
||||
detectedSubjectKey = key
|
||||
detectedSubjectName = sub.name
|
||||
hasExplicitSubject = true
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -188,10 +196,12 @@ export function parseRouteSlug(slugInput: string | string[]): ParsedRouteParams
|
||||
dong: matchedRegion.dong,
|
||||
shortRegionName,
|
||||
grade: detectedGrade,
|
||||
gradeShort,
|
||||
subjectKey: detectedSubjectKey,
|
||||
subjectName: detectedSubjectName,
|
||||
targetTerm: detectedTerm,
|
||||
fullKeyword
|
||||
fullKeyword,
|
||||
isRegionOnly: !hasExplicitGrade && !hasExplicitSubject
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+268
-95
@@ -1,150 +1,315 @@
|
||||
import type { ParsedRouteParams } from './dataHelper'
|
||||
import type { ParsedRouteParams } from './dataHelper'
|
||||
import { getShortRegionName } from './dataHelper'
|
||||
|
||||
const SUBJECT_IMAGE_MAP: Record<string, string> = {
|
||||
math: '/img/math-tutoring.png',
|
||||
english: '/img/english-tutoring.png',
|
||||
korean: '/img/korean-tutoring.png',
|
||||
science: '/img/science-tutoring.png',
|
||||
social: '/img/social-studies-tutoring.png'
|
||||
}
|
||||
|
||||
const CORE_SUBJECTS = [
|
||||
{ key: 'math', name: '수학', desc: '개념 구멍 추적 및 킬러 문항 1:1 완파', img: '/img/math-tutoring.png' },
|
||||
{ key: 'english', name: '영어', desc: '교과서·부교재 전 지문 변형 및 서술형 영작', img: '/img/english-tutoring.png' },
|
||||
{ key: 'korean', name: '국어', desc: '비문학 독해 논리와 교과서 문학 1:1 정밀 분석', img: '/img/korean-tutoring.png' },
|
||||
{ key: 'science', name: '과학', desc: '통합과학 원리 이해 및 물화생지 킬러 단원 정복', img: '/img/science-tutoring.png' },
|
||||
{ key: 'social', name: '사회', desc: '통합사회·한국사 핵심 흐름 및 사탐 만점 전략', img: '/img/social-studies-tutoring.png' }
|
||||
]
|
||||
|
||||
const CORE_GRADES = ['초등학생', '중학생', '고등학생']
|
||||
|
||||
export function generateSeoMeta(parsed: ParsedRouteParams, siteName = '쌤인증', siteDomain = 'https://soohyun.xyz') {
|
||||
const { sido, sigungu, dong, shortRegionName, grade, subjectName, targetTerm, fullKeyword } = parsed
|
||||
const { sido, sigungu, dong, shortRegionName, grade, subjectKey, subjectName, isRegionOnly } = parsed
|
||||
|
||||
const shortSido = parsed.shortSido || sido.replace(/특별시|광역시|특별자치시|도/g, '').trim()
|
||||
const gradeShort = parsed.gradeShort || (grade === '초등학생' ? '초등' : grade === '고등학생' ? '고등' : '중등')
|
||||
const regionTitle = shortRegionName || getShortRegionName(sido, dong)
|
||||
const subjectImg = SUBJECT_IMAGE_MAP[subjectKey] || '/img/math-tutoring.png'
|
||||
|
||||
// 학부모 검색 최적화: [지역] [과목]과외 전문 매칭 | [학년] 1:1 방문 맞춤 수업 (공백 포함 30~35자 내외 말줄임 방지)
|
||||
const pageTitle = `${regionTitle} ${subjectName}과외 전문 매칭 | ${grade} 1:1 방문 맞춤 수업`
|
||||
// 검색어 [지역] [초등/중등/고등] [과목] 과외 / [지역] [초등/중등/고등] 과외 완벽 매칭 타이틀
|
||||
const pageTitle = isRegionOnly
|
||||
? `${shortSido} 중등 수학 과외 · ${shortSido} 중등 과외 | ${regionTitle} 초·중·고 1:1 맞춤 과외`
|
||||
: `${shortSido} ${gradeShort} ${subjectName} 과외 · ${regionTitle} ${grade} ${subjectName}과외 | ${shortSido} ${gradeShort} 과외 1:1 매칭`
|
||||
|
||||
// 고의도 검색 및 전환 최적화 메타 디스크립션 (과외 비용 시세 + 학교 기출 분석 + 100% 신원 검증 + 무료 시범수업)
|
||||
const pageDescription = `${regionTitle} ${grade} ${subjectName}과외 전문 매칭. 과외 비용 및 시세 비교부터 무료 시범수업 신청까지. ${dong} 인근 학교별 기출 족보 완벽 분석, 서술형 감점 방지, 검증된 1:1 전문 선생님 100% 매칭.`
|
||||
// 첫 문장이 정확한 타겟 키워드로 시작하는 고의도 메타 디스크립션 (더세이브 동기화)
|
||||
const pageDescription = isRegionOnly
|
||||
? `${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${dong} 중등 수학 과외 전문 ${siteName}! ${regionTitle} 초등·중등·고등 국영수사과 1:1 맞춤 과외, 검증된 전문 선생님 무료 시범수업과 과외비 비교를 확인하세요.`
|
||||
: `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외 전문 ${siteName}! ${regionTitle} ${grade} ${subjectName} 1:1 방문·화상 맞춤 수업으로 내신·수능 성적 향상을 책임집니다. 과외비 투명 공개 및 무료 시범수업 신청하세요.`
|
||||
|
||||
// 표준 Canonical URL
|
||||
const canonicalUrl = `${siteDomain}/${encodeURIComponent(dong)}/${encodeURIComponent(grade)}/${encodeURIComponent(subjectName)}`
|
||||
const pageKeywords = `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외, ${dong} ${gradeShort} 과외, ${shortSido}${gradeShort}${subjectName}과외, ${shortSido}${gradeShort}과외, ${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${shortSido} 고등 수학 과외, ${shortSido} 초등 수학 과외, ${regionTitle} ${subjectName}과외, ${dong} ${subjectName}과외, ${dong} ${grade} ${subjectName}과외, ${dong}과외, ${sigungu}과외, ${shortSido}과외`
|
||||
|
||||
const jsonLd = {
|
||||
// 표준 Canonical URL (자기참조 분리)
|
||||
const dongUrl = `${siteDomain}/${encodeURIComponent(dong)}`
|
||||
const canonicalUrl = isRegionOnly
|
||||
? dongUrl
|
||||
: `${siteDomain}/${encodeURIComponent(dong)}/${encodeURIComponent(grade)}/${encodeURIComponent(subjectName)}`
|
||||
|
||||
// 더세이브 방식 멀티 이미지 배열
|
||||
const orgImages = [
|
||||
`${siteDomain}/img/og-main.png`,
|
||||
`${siteDomain}/img/math-tutoring.png`,
|
||||
`${siteDomain}/img/english-tutoring.png`,
|
||||
`${siteDomain}/img/korean-tutoring.png`,
|
||||
`${siteDomain}/img/science-tutoring.png`,
|
||||
`${siteDomain}/img/social-studies-tutoring.png`
|
||||
]
|
||||
|
||||
// 서브 라우터 캐러셀(ItemList) 항목 구성: 현재 동 기준 5대 과목 + 타 학년 라우터 (중복 제외 총 7개)
|
||||
const carouselCourses: Array<{ name: string, desc: string, url: string, image: string }> = []
|
||||
for (const sub of CORE_SUBJECTS) {
|
||||
const cUrl = `${siteDomain}/${encodeURIComponent(dong)}/${encodeURIComponent(grade)}/${encodeURIComponent(sub.name)}`
|
||||
carouselCourses.push({
|
||||
name: `${shortSido} ${gradeShort} ${sub.name} 과외 · ${regionTitle} ${grade} ${sub.name}과외`,
|
||||
desc: `${dong} 관내 학교 내신 기출 분석 및 ${sub.desc}`,
|
||||
url: cUrl,
|
||||
image: `${siteDomain}${sub.img}`
|
||||
})
|
||||
}
|
||||
for (const g of CORE_GRADES) {
|
||||
if (g === grade) continue
|
||||
const gShort = g === '초등학생' ? '초등' : g === '고등학생' ? '고등' : '중등'
|
||||
const cUrl = `${siteDomain}/${encodeURIComponent(dong)}/${encodeURIComponent(g)}/${encodeURIComponent(subjectName)}`
|
||||
carouselCourses.push({
|
||||
name: `${shortSido} ${gShort} ${subjectName} 과외 · ${regionTitle} ${g} ${subjectName}과외`,
|
||||
desc: `${dong} ${g} 학생 대상 ${subjectName} 내신·선행 1:1 밀착 지도`,
|
||||
url: cUrl,
|
||||
image: `${siteDomain}${subjectImg}`
|
||||
})
|
||||
}
|
||||
|
||||
const breadcrumbItems = isRegionOnly
|
||||
? [
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
'position': 1,
|
||||
'name': '홈',
|
||||
'item': `${siteDomain}/`
|
||||
},
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
'position': 2,
|
||||
'name': `${regionTitle} 과외`,
|
||||
'item': dongUrl
|
||||
}
|
||||
]
|
||||
: [
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
'position': 1,
|
||||
'name': '홈',
|
||||
'item': `${siteDomain}/`
|
||||
},
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
'position': 2,
|
||||
'name': `${dong} 과외`,
|
||||
'item': dongUrl
|
||||
},
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
'position': 3,
|
||||
'name': `${shortSido} ${gradeShort} ${subjectName} 과외 · ${dong} ${grade} ${subjectName}과외`,
|
||||
'item': canonicalUrl
|
||||
}
|
||||
]
|
||||
|
||||
// 블록 1: @graph (WebSite + WebPage + ImageObject + EducationalOrganization + LocalBusiness + Course + BreadcrumbList)
|
||||
const graphJsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@graph': [
|
||||
{
|
||||
'@type': 'WebSite',
|
||||
'@id': `${siteDomain}/#site`,
|
||||
'url': `${siteDomain}/`,
|
||||
'name': siteName,
|
||||
'inLanguage': 'ko-KR'
|
||||
},
|
||||
{
|
||||
'@type': 'WebPage',
|
||||
'@id': `${canonicalUrl}#page`,
|
||||
'url': canonicalUrl,
|
||||
'name': pageTitle,
|
||||
'description': pageDescription,
|
||||
'isPartOf': { '@id': `${siteDomain}/#site` },
|
||||
'primaryImageOfPage': { '@id': `${canonicalUrl}#img` },
|
||||
'inLanguage': 'ko-KR'
|
||||
},
|
||||
{
|
||||
'@type': 'ImageObject',
|
||||
'@id': `${canonicalUrl}#img`,
|
||||
'url': `${siteDomain}${subjectImg}`,
|
||||
'contentUrl': `${siteDomain}${subjectImg}`,
|
||||
'width': 1080,
|
||||
'height': 1080,
|
||||
'caption': `${shortSido} ${gradeShort} ${subjectName} 과외`
|
||||
},
|
||||
{
|
||||
'@type': 'EducationalOrganization',
|
||||
'name': siteName,
|
||||
'url': siteDomain,
|
||||
'image': orgImages,
|
||||
'telephone': '+82-10-5961-5807',
|
||||
'description': pageDescription,
|
||||
'areaServed': {
|
||||
'@type': 'AdministrativeArea',
|
||||
'name': `${sido} ${sigungu} ${dong}`
|
||||
}
|
||||
},
|
||||
{
|
||||
'@type': 'Course',
|
||||
'name': `${dong} ${grade} ${subjectName} 1:1 맞춤 과외`,
|
||||
'description': `${dong} 인근 학교 내신 출제 경향 분석 및 ${subjectName} 1등급 도약 커리큘럼`,
|
||||
'provider': {
|
||||
'@type': 'Organization',
|
||||
'name': siteName
|
||||
}
|
||||
},
|
||||
{
|
||||
'@type': 'LocalBusiness',
|
||||
'name': `${regionTitle} 1:1 방문 과외`,
|
||||
'image': `${siteDomain}/img/og-preview.png`,
|
||||
'name': `${shortSido} ${gradeShort} 과외 · ${regionTitle} 1:1 방문 과외`,
|
||||
'image': orgImages,
|
||||
'url': canonicalUrl,
|
||||
'telephone': '+82-10-5961-5807',
|
||||
'priceRange': '₩₩',
|
||||
'address': {
|
||||
'@type': 'PostalAddress',
|
||||
'addressLocality': sido,
|
||||
'addressRegion': sigungu,
|
||||
'addressLocality': sigungu,
|
||||
'addressRegion': sido,
|
||||
'streetAddress': dong,
|
||||
'addressCountry': 'KR'
|
||||
},
|
||||
'areaServed': `${sido} ${sigungu} ${dong}`
|
||||
},
|
||||
{
|
||||
'@type': 'FAQPage',
|
||||
'mainEntity': [
|
||||
{
|
||||
'@type': 'Question',
|
||||
'name': `${dong} ${grade} ${subjectName} 방문 과외와 온라인 화상 과외 중 어떤 것이 가능한가요?`,
|
||||
'acceptedAnswer': {
|
||||
'@type': 'Answer',
|
||||
'text': `학생과 학부모님의 선호에 따라 100% 자택 방문 대면 과외 또는 실시간 쌍방향 비대면 화상 과외 중 자유롭게 선택하실 수 있습니다. ${dong} 전 지역 방문 수업이 가능합니다.`
|
||||
}
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
'name': `${grade} ${subjectName} 전문 선생님이 학생과 잘 맞지 않으면 교체 가능한가요?`,
|
||||
'acceptedAnswer': {
|
||||
'@type': 'Answer',
|
||||
'text': '네, 1회 무료 모의수업을 통해 사전에 호흡을 맞추며, 정규 수업 시작 후에도 학생의 성향이나 요청에 따라 추가 비용 없이 100% 무료 교체 및 환불 규정을 보장합니다.'
|
||||
}
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
'name': `${grade} ${subjectName} 과외 수업 비용은 어떻게 되나요?`,
|
||||
'acceptedAnswer': {
|
||||
'@type': 'Answer',
|
||||
'text': `${grade} 내신 단계, 주당 수업 횟수, 수업 시간(1회 60분/90분/120분)에 따라 표준 교육청 가이드라인에 맞춘 투명한 정찰제로 운영됩니다. 무료 상담 시 상세 견적표를 안내해 드립니다.`
|
||||
}
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
'name': `${dong} 학교 중간·기말고사 직전 단기 ${subjectName} 족보 클리닉도 가능한가요?`,
|
||||
'acceptedAnswer': {
|
||||
'@type': 'Answer',
|
||||
'text': `네, 시험 3~4주 전 학교별 기출 족보 풀이 및 서술형 배점 방어를 위한 ${grade} ${subjectName} 단기 속성 킬러 클리닉 프로그램도 상시 운영 중입니다.`
|
||||
}
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
'name': `${subjectName} 외에 다른 과목도 함께 상담받을 수 있나요?`,
|
||||
'acceptedAnswer': {
|
||||
'@type': 'Answer',
|
||||
'text': `${subjectName} 집중 신청은 물론, 국영수 또는 수탐 복합 패키지로 맞춤 구성하여 전 과목 플래너 관리와 함께 시간표를 편성하실 수 있습니다.`
|
||||
}
|
||||
}
|
||||
]
|
||||
'@type': 'Course',
|
||||
'name': isRegionOnly ? `${shortSido} 중등 수학 과외 · ${regionTitle} 초·중·고 1:1 맞춤 과외` : `${shortSido} ${gradeShort} ${subjectName} 과외 · ${dong} ${grade} ${subjectName} 1:1 맞춤 과외`,
|
||||
'description': `${shortSido} ${gradeShort} ${subjectName} 과외 및 ${dong} 인근 학교 내신 출제 경향 분석, ${subjectName} 1등급 도약 커리큘럼`,
|
||||
'url': canonicalUrl,
|
||||
'image': `${siteDomain}${subjectImg}`,
|
||||
'provider': {
|
||||
'@type': 'Organization',
|
||||
'name': siteName,
|
||||
'sameAs': siteDomain
|
||||
},
|
||||
'offers': {
|
||||
'@type': 'Offer',
|
||||
'category': 'Paid',
|
||||
'priceCurrency': 'KRW',
|
||||
'availability': 'https://schema.org/InStock',
|
||||
'url': canonicalUrl
|
||||
},
|
||||
'hasCourseInstance': {
|
||||
'@type': 'CourseInstance',
|
||||
'courseMode': ['onsite', 'online']
|
||||
}
|
||||
},
|
||||
{
|
||||
'@type': 'BreadcrumbList',
|
||||
'itemListElement': [
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
'position': 1,
|
||||
'name': '홈',
|
||||
'item': siteDomain
|
||||
},
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
'position': 2,
|
||||
'name': sido,
|
||||
'item': `${siteDomain}/${encodeURIComponent(sido)}`
|
||||
},
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
'position': 3,
|
||||
'name': sigungu,
|
||||
'item': `${siteDomain}/${encodeURIComponent(sigungu)}`
|
||||
},
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
'position': 4,
|
||||
'name': `${dong} ${subjectName}`,
|
||||
'item': canonicalUrl
|
||||
}
|
||||
]
|
||||
'itemListElement': breadcrumbItems
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 블록 2: FAQPage 단독 JSON-LD
|
||||
const faqJsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
'mainEntity': [
|
||||
{
|
||||
'@type': 'Question',
|
||||
'name': `${shortSido} ${gradeShort} ${subjectName} 과외(${dong}), 방문 수업과 화상 수업 모두 가능한가요?`,
|
||||
'acceptedAnswer': {
|
||||
'@type': 'Answer',
|
||||
'text': `네, ${shortSido} ${gradeShort} ${subjectName} 과외 및 ${shortSido} ${gradeShort} 과외는 학생과 학부모님의 선호에 따라 100% 자택 방문 대면 수업 또는 실시간 쌍방향 화상 수업 중 자유롭게 선택하실 수 있으며, ${dong} ${gradeShort} ${subjectName} 과외 전 지역 방문 지도가 가능합니다.`
|
||||
}
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
'name': `${shortSido} ${gradeShort} 과외 · ${dong} ${gradeShort} ${subjectName} 과외 전문 선생님이 학생과 맞지 않으면 교체 가능한가요?`,
|
||||
'acceptedAnswer': {
|
||||
'@type': 'Answer',
|
||||
'text': `네, ${shortSido} ${gradeShort} ${subjectName} 과외 및 ${shortSido} ${gradeShort} 과외는 1회 무료 시범수업을 통해 사전에 호흡을 맞추며, ${dong} ${gradeShort} ${subjectName} 과외 정규 수업 시작 후에도 추가 비용 없이 100% 무료 선생님 교체 및 환불 규정을 보장합니다.`
|
||||
}
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
'name': `${shortSido} ${gradeShort} ${subjectName} 과외 및 ${dong} ${gradeShort} ${subjectName} 과외 수업 비용은 어떻게 되나요?`,
|
||||
'acceptedAnswer': {
|
||||
'@type': 'Answer',
|
||||
'text': `${shortSido} ${gradeShort} ${subjectName} 과외와 ${shortSido} ${gradeShort} 과외 비용은 ${grade} 내신 단계, 주당 수업 횟수, 수업 시간(1회 60분/90분/120분)에 따라 표준 교육청 가이드라인에 맞춘 투명한 정찰제로 운영됩니다. ${dong} ${gradeShort} ${subjectName} 과외 무료 상담 시 상세 견적표를 안내해 드립니다.`
|
||||
}
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
'name': `${dong} ${gradeShort} ${subjectName} 과외로 중간·기말고사 직전 단기 족보 클리닉도 가능한가요?`,
|
||||
'acceptedAnswer': {
|
||||
'@type': 'Answer',
|
||||
'text': `네, ${shortSido} ${gradeShort} ${subjectName} 과외 및 ${shortSido} ${gradeShort} 과외 전담팀이 시험 3~4주 전 ${dong} 인근 학교별 기출 족보 풀이 및 서술형 배점 방어를 위한 ${dong} ${gradeShort} ${subjectName} 과외 단기 속성 클리닉도 상시 운영 중입니다.`
|
||||
}
|
||||
},
|
||||
{
|
||||
'@type': 'Question',
|
||||
'name': `${shortSido} ${gradeShort} ${subjectName} 과외 외에 국영수사과 다른 과목도 함께 상담받을 수 있나요?`,
|
||||
'acceptedAnswer': {
|
||||
'@type': 'Answer',
|
||||
'text': `${shortSido} ${gradeShort} ${subjectName} 과외 집중 신청은 물론, ${shortSido} ${gradeShort} 과외 국영수사과 복합 패키지 및 ${dong} ${gradeShort} ${subjectName} 과외 맞춤형 전 과목 플래너 관리로 시간표를 편성하실 수 있습니다.`
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 블록 3: ItemList 단독 JSON-LD (더세이브 검색결과 가로 이미지 줄 핵심 구조)
|
||||
const itemListJsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'ItemList',
|
||||
'name': `${shortSido} ${gradeShort} ${subjectName} 과외 · ${dong} ${gradeShort} 과외`,
|
||||
'numberOfItems': carouselCourses.length,
|
||||
'itemListElement': carouselCourses.map((c, idx) => ({
|
||||
'@type': 'ListItem',
|
||||
'position': idx + 1,
|
||||
'name': c.name,
|
||||
'url': c.url,
|
||||
'image': c.image,
|
||||
'item': {
|
||||
'@type': 'Course',
|
||||
'name': c.name,
|
||||
'description': c.desc,
|
||||
'url': c.url,
|
||||
'image': c.image,
|
||||
'provider': {
|
||||
'@type': 'Organization',
|
||||
'name': siteName,
|
||||
'sameAs': siteDomain
|
||||
},
|
||||
'offers': {
|
||||
'@type': 'Offer',
|
||||
'category': 'Paid',
|
||||
'priceCurrency': 'KRW',
|
||||
'availability': 'https://schema.org/InStock',
|
||||
'url': c.url
|
||||
},
|
||||
'hasCourseInstance': {
|
||||
'@type': 'CourseInstance',
|
||||
'courseMode': ['onsite', 'online']
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
return {
|
||||
title: pageTitle,
|
||||
meta: [
|
||||
{ name: 'robots', content: 'index,follow' },
|
||||
{ name: 'naver-site-verification', content: 'd40ee8cd61dab827dcbd7080e54b095339c44222' },
|
||||
{ name: 'description', content: pageDescription },
|
||||
{ name: 'keywords', content: `${dong}과외, ${dong}${subjectName}과외, ${dong}${grade}과외, ${sigungu}과외, ${dong}학원, ${dong}내신대비, ${subjectName}1:1과외` },
|
||||
{ name: 'keywords', content: pageKeywords },
|
||||
{ property: 'og:type', content: 'website' },
|
||||
{ property: 'og:locale', content: 'ko_KR' },
|
||||
{ property: 'og:title', content: pageTitle },
|
||||
{ property: 'og:description', content: pageDescription },
|
||||
{ property: 'og:url', content: canonicalUrl },
|
||||
{ property: 'og:site_name', content: siteName },
|
||||
{ property: 'og:image', content: `${siteDomain}/img/og-preview.png` },
|
||||
{ property: 'og:image', content: `${siteDomain}${subjectImg}` },
|
||||
{ property: 'og:image:type', content: 'image/png' },
|
||||
{ property: 'og:image:width', content: '1200' },
|
||||
{ property: 'og:image:height', content: '630' },
|
||||
{ property: 'og:image:alt', content: `${shortSido} ${gradeShort} ${subjectName} 과외 · ${dong} ${gradeShort} ${subjectName} 과외` },
|
||||
{ name: 'twitter:card', content: 'summary_large_image' },
|
||||
{ name: 'twitter:title', content: pageTitle },
|
||||
{ name: 'twitter:description', content: pageDescription }
|
||||
{ name: 'twitter:description', content: pageDescription },
|
||||
{ name: 'twitter:image', content: `${siteDomain}${subjectImg}` }
|
||||
],
|
||||
link: [
|
||||
{ rel: 'canonical', href: canonicalUrl }
|
||||
@@ -152,7 +317,15 @@ export function generateSeoMeta(parsed: ParsedRouteParams, siteName = '쌤인증
|
||||
script: [
|
||||
{
|
||||
type: 'application/ld+json',
|
||||
children: JSON.stringify(jsonLd)
|
||||
children: JSON.stringify(graphJsonLd)
|
||||
},
|
||||
{
|
||||
type: 'application/ld+json',
|
||||
children: JSON.stringify(faqJsonLd)
|
||||
},
|
||||
{
|
||||
type: 'application/ld+json',
|
||||
children: JSON.stringify(itemListJsonLd)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user