338 lines
15 KiB
TypeScript
338 lines
15 KiB
TypeScript
import type { ParsedRouteParams } from './dataHelper'
|
|
import { getShortRegionName, getShortSido } 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 SUBJECT_LIST = [
|
|
{ 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 GRADE_LIST = ['초등학생', '중학생', '고등학생']
|
|
|
|
export function generateSeoMeta(parsed: ParsedRouteParams, siteName = '튜터온', siteDomain = 'https://aeseo.site') {
|
|
const { sido, sigungu, dong, shortRegionName, grade, subjectKey, subjectName, isRegionOnly } = parsed
|
|
|
|
const shortSido = getShortSido(sido) || '인천'
|
|
const gradeShort = parsed.gradeShort || (grade === '초등학생' ? '초등' : grade === '고등학생' ? '고등' : '중등')
|
|
const regionTitle = shortRegionName || getShortRegionName(sido, dong, sigungu)
|
|
const subjectImg = SUBJECT_IMAGE_MAP[subjectKey] || '/img/math-tutoring.png'
|
|
|
|
const dongHash = Array.from(dong || '').reduce((acc, ch) => acc + ch.charCodeAt(0), 0)
|
|
const baseSubjects = ['수학', '영어', '국어', '과학', '사회']
|
|
const rotatedSubjects = baseSubjects.map((_, i) => baseSubjects[(dongHash + i) % baseSubjects.length])
|
|
const rotatedSubText = rotatedSubjects.map(s => `${dong}${s}과외선생님`).join(', ')
|
|
|
|
const pageTitle = isRegionOnly
|
|
? `${shortSido} 중등 수학 과외 · ${shortSido} 중등 과외 | ${regionTitle} 초·중·고 1:1 맞춤 과외`
|
|
: `${shortSido} ${gradeShort} ${subjectName} 과외 · ${regionTitle} ${grade} ${subjectName}과외 | ${shortSido} ${gradeShort} 과외 1:1 매칭`
|
|
|
|
const pageDescription = isRegionOnly
|
|
? `${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${dong} 중등 수학 과외 전문 ${siteName}! ${regionTitle} 초등·중등·고등 국영수사과 1:1 맞춤 과외와 검증된 전문 선생님 무료 시범수업, 과외비 비교(${rotatedSubText})를 확인하세요.`
|
|
: `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외 전문 ${siteName}! ${regionTitle} ${grade} ${subjectName} 1:1 방문·화상 맞춤 수업으로 내신·수능 성적 향상을 책임집니다. 과외비 투명 공개 및 무료 시범수업(${rotatedSubText})을 신청하세요.`
|
|
|
|
const dongUrl = `${siteDomain}/${encodeURIComponent(dong)}`
|
|
const canonicalUrl = isRegionOnly
|
|
? dongUrl
|
|
: `${siteDomain}/${encodeURIComponent(dong)}/${encodeURIComponent(grade)}/${encodeURIComponent(subjectName)}`
|
|
|
|
const allBrandImages = [
|
|
`${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 SUBJECT_LIST) {
|
|
const cUrl = `${siteDomain}/${encodeURIComponent(dong)}/${encodeURIComponent(grade)}/${encodeURIComponent(sub.name)}`
|
|
carouselCourses.push({
|
|
name: `${shortSido} ${gradeShort} ${sub.name} 과외 · ${regionTitle} ${grade} ${sub.name}과외 1:1 맞춤 수업`,
|
|
desc: `${shortSido} ${gradeShort} ${sub.name} 과외 및 ${dong} ${grade} ${sub.name} 내신·수능 대비 — ${sub.desc}`,
|
|
url: cUrl,
|
|
image: `${siteDomain}${sub.img}`
|
|
})
|
|
}
|
|
|
|
for (const g of GRADE_LIST) {
|
|
if (g !== grade) {
|
|
const gShort = g === '초등학생' ? '초등' : g === '고등학생' ? '고등' : '중등'
|
|
const cUrl = `${siteDomain}/${encodeURIComponent(dong)}/${encodeURIComponent(g)}/${encodeURIComponent(subjectName)}`
|
|
carouselCourses.push({
|
|
name: `${shortSido} ${gShort} ${subjectName} 과외 · ${regionTitle} ${g} ${subjectName}과외 1:1 맞춤 수업`,
|
|
desc: `${shortSido} ${gShort} 과외 및 ${dong} ${g} ${subjectName} 내신 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})`,
|
|
'item': canonicalUrl
|
|
}
|
|
]
|
|
|
|
// 블록 1: @graph (WebSite + WebPage + ImageObject + EducationalOrganization + LocalBusiness + Course + BreadcrumbList)
|
|
const jsonLdGraph = {
|
|
'@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',
|
|
'@id': `${siteDomain}/#org`,
|
|
'name': siteName,
|
|
'url': `${siteDomain}/`,
|
|
'image': allBrandImages,
|
|
'description': pageDescription,
|
|
'telephone': '+82-10-5961-5807',
|
|
'areaServed': {
|
|
'@type': 'AdministrativeArea',
|
|
'name': `${sido} ${sigungu} ${dong}`
|
|
}
|
|
},
|
|
{
|
|
'@type': 'LocalBusiness',
|
|
'@id': `${canonicalUrl}#business`,
|
|
'name': `${shortSido} ${gradeShort} ${subjectName} 과외 · ${regionTitle} 1:1 방문 과외`,
|
|
'image': allBrandImages,
|
|
'url': canonicalUrl,
|
|
'telephone': '+82-10-5961-5807',
|
|
'priceRange': '₩₩',
|
|
'address': {
|
|
'@type': 'PostalAddress',
|
|
'addressLocality': sigungu,
|
|
'addressRegion': sido,
|
|
'streetAddress': dong,
|
|
'addressCountry': 'KR'
|
|
},
|
|
'areaServed': `${sido} ${sigungu} ${dong}`
|
|
},
|
|
{
|
|
'@type': 'Course',
|
|
'@id': `${canonicalUrl}#course`,
|
|
'name': `${shortSido} ${gradeShort} ${subjectName} 과외 · ${regionTitle} ${grade} ${subjectName} 1:1 맞춤 과외`,
|
|
'description': `${shortSido} ${gradeShort} ${subjectName} 과외 및 ${shortSido} ${gradeShort} 과외 전문 매칭 — ${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',
|
|
'@id': `${canonicalUrl}#breadcrumb`,
|
|
'itemListElement': breadcrumbItems
|
|
}
|
|
]
|
|
}
|
|
|
|
// 블록 2: FAQPage 단독 JSON-LD (정확한 타겟 키워드 Q&A 전면 배치)
|
|
const jsonLdFaq = {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'FAQPage',
|
|
'mainEntity': [
|
|
{
|
|
'@type': 'Question',
|
|
'name': `${shortSido} ${gradeShort} ${subjectName} 과외(${dong}), 방문 수업과 온라인 화상 수업 모두 가능한가요?`,
|
|
'acceptedAnswer': {
|
|
'@type': 'Answer',
|
|
'text': `네, ${shortSido} ${gradeShort} ${subjectName} 과외 및 ${shortSido} ${gradeShort} 과외는 학생과 학부모님의 선호에 따라 ${dong} 100% 자택 방문 대면 과외 또는 실시간 쌍방향 화상 과외 중 자유롭게 선택하실 수 있습니다.`
|
|
}
|
|
},
|
|
{
|
|
'@type': 'Question',
|
|
'name': `${shortSido} ${gradeShort} 과외 및 ${dong} ${gradeShort} ${subjectName} 과외 선생님이 학생과 맞지 않으면 교체 가능한가요?`,
|
|
'acceptedAnswer': {
|
|
'@type': 'Answer',
|
|
'text': `네, ${shortSido} ${gradeShort} ${subjectName} 과외는 1회 무료 시범수업을 통해 사전에 호흡을 맞추며, 정규 수업 시작 후에도 학생 성향이나 요청에 따라 추가 비용 없이 100% 무료 교체 및 환불 규정을 보장합니다.`
|
|
}
|
|
},
|
|
{
|
|
'@type': 'Question',
|
|
'name': `${shortSido} ${gradeShort} ${subjectName} 과외(${dong} ${grade} ${subjectName}과외) 수업 비용은 어떻게 되나요?`,
|
|
'acceptedAnswer': {
|
|
'@type': 'Answer',
|
|
'text': `${shortSido} ${gradeShort} ${subjectName} 과외 및 ${shortSido} ${gradeShort} 과외 수업료는 ${grade} 내신 단계, 주당 횟수, 수업 시간(60분/90분/120분)에 따라 교육청 가이드라인에 맞춘 투명한 정찰제로 운영됩니다.`
|
|
}
|
|
},
|
|
{
|
|
'@type': 'Question',
|
|
'name': `${dong} 학교 중간·기말고사 대비 ${shortSido} ${gradeShort} ${subjectName} 과외 단기 족보 클리닉도 가능한가요?`,
|
|
'acceptedAnswer': {
|
|
'@type': 'Answer',
|
|
'text': `네, 시험 3~4주 전 ${dong} 인근 학교별 기출 족보 풀이 및 서술형 배점 방어를 위한 ${shortSido} ${gradeShort} ${subjectName} 과외 단기 속성 킬러 클리닉 프로그램도 상시 운영 중입니다.`
|
|
}
|
|
},
|
|
{
|
|
'@type': 'Question',
|
|
'name': `${shortSido} ${gradeShort} ${subjectName} 과외 외에 다른 과목도 함께 ${shortSido} ${gradeShort} 과외 상담을 받을 수 있나요?`,
|
|
'acceptedAnswer': {
|
|
'@type': 'Answer',
|
|
'text': `${shortSido} ${gradeShort} ${subjectName} 과외 집중 신청은 물론, 국영수 또는 수탐 복합 ${shortSido} ${gradeShort} 과외 패키지로 맞춤 구성하여 전 과목 플래너 관리와 함께 시간표를 편성하실 수 있습니다.`
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
// 블록 3: ItemList 단독 JSON-LD (최상위 name, url, image + item: Course 동시 제공)
|
|
const jsonLdItemList = {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'ItemList',
|
|
'name': `${shortSido} ${gradeShort} ${subjectName} 과외 · ${dong} ${gradeShort} 과외`,
|
|
'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: '797ab1ac988eb27c80ae544bb6aba05c2231a9c9' },
|
|
{ name: 'description', content: pageDescription },
|
|
{ name: 'keywords', content: `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${shortSido}${gradeShort}${subjectName}과외, ${shortSido}${gradeShort}과외, ${dong} ${gradeShort} ${subjectName} 과외, ${dong} ${gradeShort} 과외, ${shortSido} 중등 수학 과외, ${shortSido} 중등 과외, ${shortSido} 고등 수학 과외, ${shortSido} 초등 수학 과외, ${regionTitle} ${subjectName}과외, ${dong} ${subjectName}과외, ${dong} ${grade} ${subjectName}과외, ${dong}과외, ${sigungu}과외, ${shortSido}과외` },
|
|
{ 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}${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:image', content: `${siteDomain}${subjectImg}` }
|
|
],
|
|
link: [
|
|
{ rel: 'canonical', href: canonicalUrl }
|
|
],
|
|
script: [
|
|
{
|
|
type: 'application/ld+json',
|
|
children: JSON.stringify(jsonLdGraph)
|
|
},
|
|
{
|
|
type: 'application/ld+json',
|
|
children: JSON.stringify(jsonLdFaq)
|
|
},
|
|
{
|
|
type: 'application/ld+json',
|
|
children: JSON.stringify(jsonLdItemList)
|
|
}
|
|
]
|
|
}
|
|
}
|