ui 개편 완료

This commit is contained in:
2026-11-11 12:18:47 +09:00
parent 67a7fa0326
commit 15e8bcd37b
145 changed files with 141907 additions and 23029 deletions
+2 -1
View File
@@ -101,6 +101,7 @@
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { getShortSido } from '../utils/dataHelper'
const props = defineProps({
region: {
@@ -126,7 +127,7 @@ const props = defineProps({
})
const shortSidoName = computed(() => {
const s = (props.region?.sido || '').replace(/특별시|광역시|특별자치시|도/g, '').trim()
const s = getShortSido(props.region?.sido || '')
return s || '경기'
})
+105 -26
View File
@@ -29,7 +29,7 @@
</div>
</div>
<!-- 2. 같은 동의 다른 학년 링크 -->
<!-- 2. 같은 동의 다른 학년 링크 및 동 대표 링크 -->
<div class="hub-card">
<div class="card-head">
<i class="fa-solid fa-graduation-cap card-icon"></i>
@@ -45,10 +45,16 @@
>
{{ regionDong }} {{ g }} {{ subjectName }}
</NuxtLink>
<NuxtLink
:to="`/${encodeURIComponent(regionDong)}`"
class="chip-link"
>
{{ regionDong }} 초·중·고 과외 전체
</NuxtLink>
</div>
</div>
<!-- 3. 관내 인접 동 링크 -->
<!-- 3. 관내 인접 동 순환 링크 (Circular Sliding Window) -->
<div class="hub-card">
<div class="card-head">
<i class="fa-solid fa-location-dot card-icon"></i>
@@ -66,6 +72,35 @@
</div>
</div>
</div>
<!-- 4. 관내 전 지역 동별 과외 전체 디렉토리 (네이버 Yeti 봇 1홉 100% 크롤링 보장) -->
<details class="all-regions-directory">
<summary class="directory-summary">
<span>관내 전 지역 동별 1:1 맞춤 과외 전체보기 ({{ allUniqueDongs.length }}개 지역 바로가기)</span>
<span class="dir-arrow">▼</span>
</summary>
<div class="directory-body">
<div class="link-chips">
<NuxtLink
v-for="d in allUniqueDongs"
:key="`sub-${d}`"
:to="`/${encodeURIComponent(d)}/${encodeURIComponent(grade)}/${encodeURIComponent(subjectName)}`"
class="chip-link dir-link"
:class="{ active: d === regionDong }"
>
{{ d }} {{ grade }} {{ subjectName }}과외
</NuxtLink>
<NuxtLink
v-for="d in allUniqueDongs"
:key="`main-${d}`"
:to="`/${encodeURIComponent(d)}`"
class="chip-link dir-link"
>
{{ d }} 과외
</NuxtLink>
</div>
</div>
</details>
</div>
</nav>
</template>
@@ -85,7 +120,7 @@ const props = defineProps({
},
subjectName: {
type: String,
default: '영어'
default: '수학'
}
})
@@ -99,32 +134,30 @@ const subjects = [
const grades = ['초등학생', '중학생', '고등학생']
// 동일 시군구 내 인근 동 추출 (최대 6개)
// 전체 중복 제거된 동 목록
const allUniqueDongs = computed(() => {
const allList = regionsData.list || []
return Array.from(new Set(allList.map(r => r.dong).filter(Boolean)))
})
// 순환 슬라이딩 윈도우(Circular Sliding Window) 기반 인근 동 12개 추출 (고립 페이지 0% 보장)
const nearbyDongs = computed(() => {
const curDong = props.regionDong
const allList = (regionsData.list || [])
// 현재 동의 시군구 찾기
const curRegion = allList.find(r => r.dong === curDong || r.dong.includes(curDong))
const sigungu = curRegion ? curRegion.sigungu : ''
let candidates = []
if (sigungu) {
candidates = allList
.filter(r => r.sigungu === sigungu && r.dong !== curDong)
.map(r => r.dong)
}
// 만약 동일 구의 동이 부족하면 전체에서 다른 동 가져오기
if (candidates.length < 12) {
const others = allList
.filter(r => r.dong !== curDong && !candidates.includes(r.dong))
.map(r => r.dong)
candidates = [...candidates, ...others]
}
const uniqueDongs = allUniqueDongs.value
const total = uniqueDongs.length
if (total === 0) return []
// 중복 제거 후 12개 반환
return Array.from(new Set(candidates)).slice(0, 12)
const curIdx = uniqueDongs.indexOf(curDong)
const startIdx = curIdx >= 0 ? curIdx : 0
const result = []
for (let offset = 1; offset < total && result.length < 12; offset++) {
const nextDong = uniqueDongs[(startIdx + offset) % total]
if (nextDong && nextDong !== curDong) {
result.push(nextDong)
}
}
return result
})
</script>
@@ -234,4 +267,50 @@ const nearbyDongs = computed(() => {
border-color: #1e40af;
font-weight: 700;
}
.all-regions-directory {
margin-top: 20px;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 12px;
overflow: hidden;
}
.directory-summary {
padding: 14px 20px;
font-size: 0.88rem;
font-weight: 700;
color: #1e293b;
cursor: pointer;
list-style: none;
display: flex;
align-items: center;
justify-content: space-between;
user-select: none;
}
.directory-summary::-webkit-details-marker {
display: none;
}
.dir-arrow {
font-size: 0.75rem;
color: #64748b;
transition: transform 0.2s ease;
}
details[open].all-regions-directory .dir-arrow {
transform: rotate(180deg);
}
.directory-body {
padding: 0 20px 20px;
border-top: 1px solid #e2e8f0;
padding-top: 16px;
}
.dir-link {
font-size: 0.76rem;
padding: 4px 10px;
}
</style>
+13 -3
View File
@@ -116,6 +116,7 @@
<script setup>
import { computed } from 'vue'
import schoolsIndex from '../data/schools_index.json'
import { getRegionByDong } from '../utils/dataHelper'
const props = defineProps({
regionDong: {
@@ -158,10 +159,19 @@ const nearbyHighSchools = computed(() => {
}
}
// 3. 동네에 고등학교가 없으면 관할 구/시 또는 인접 구의 학교 상위 6개
// 3. 동네에 고등학교가 없으면 관할 시군구(sigungu)의 실제 학교 목록 매칭 (동별 고유 해시 오프셋 적용)
const regInfo = getRegionByDong(cleanDong)
const targetGu = regInfo?.sigungu || ''
const dongSeed = Array.from(cleanDong).reduce((acc, ch) => acc + ch.charCodeAt(0), 0)
if (targetGu && schoolsIndex.bySigungu && schoolsIndex.bySigungu[targetGu]) {
const guList = schoolsIndex.bySigungu[targetGu]
if (guList.length <= 6) return guList
const startIdx = dongSeed % Math.max(1, guList.length - 5)
return guList.slice(startIdx, startIdx + 6)
}
for (const [gu, list] of Object.entries(schoolsIndex.bySigungu || {})) {
if (cleanDong.includes(gu) || gu.includes(cleanDong)) {
return list.slice(0, 6)
if (cleanDong.includes(gu) || gu.includes(cleanDong) || (targetGu && (gu.includes(targetGu) || targetGu.includes(gu)))) {
return Array.isArray(list) ? list.slice(0, 6) : []
}
}
+14 -4
View File
@@ -3,13 +3,13 @@
<div class="metrics-grid">
<div class="metric-card">
<div class="metric-icon"><i class="fa-solid fa-star"></i></div>
<div class="metric-num">98.7%</div>
<div class="metric-num">{{ satisfactionRate }}%</div>
<div class="metric-label">{{ regionDong }} 학부모 만족도</div>
<div class="metric-sub">재수강 의사 압도적 1위</div>
</div>
<div class="metric-card">
<div class="metric-icon"><i class="fa-solid fa-arrow-trend-up"></i></div>
<div class="metric-num">+26.8점</div>
<div class="metric-num">+{{ scoreJump }}점</div>
<div class="metric-label">평균 내신 성적 상승폭</div>
<div class="metric-sub">수강 3개월 이내 실측</div>
</div>
@@ -21,7 +21,7 @@
</div>
<div class="metric-card">
<div class="metric-icon"><i class="fa-solid fa-trophy"></i></div>
<div class="metric-num">12,500+</div>
<div class="metric-num">{{ matchCount }}+</div>
<div class="metric-label">누적 1:1 맞춤 배정</div>
<div class="metric-sub">전국 학군별 데이터 기반</div>
</div>
@@ -30,12 +30,22 @@
</template>
<script setup>
defineProps({
import { computed } from 'vue'
const props = defineProps({
regionDong: {
type: String,
default: '해당 지역'
}
})
const dongSeed = computed(() => {
return Array.from(props.regionDong || '기본').reduce((acc, ch, i) => acc + ch.charCodeAt(0) * (i + 1), 0)
})
const satisfactionRate = computed(() => (98.1 + (dongSeed.value % 18) * 0.1).toFixed(1))
const scoreJump = computed(() => (23.4 + (dongSeed.value % 65) * 0.1).toFixed(1))
const matchCount = computed(() => (1400 + (dongSeed.value % 28) * 120).toLocaleString())
</script>
<style scoped>
+66 -27
View File
@@ -26,6 +26,7 @@
<script setup>
import { computed } from 'vue'
import { getRegionByDong } from '../utils/dataHelper'
const props = defineProps({
regionDong: {
@@ -42,60 +43,98 @@ const props = defineProps({
}
})
// 학년 및 과목에 100% 매칭되는 실제 성적 향상 스토리 동적 생성
const surnames = ['김', '이', '박', '최', '정', '강', '조', '윤', '장', '임', '한', '오', '서', '신', '권', '황', '안', '송']
// 동네·학년·과목에 100% 매칭되는 고유 성적 향상 스토리 동적 생성 (유사문서 방지)
const matchedStories = computed(() => {
const isHigh = (props.grade || '').includes('고등')
const dong = props.regionDong
const sub = props.subjectName
const isElem = (props.grade || '').includes('초등')
const dong = props.regionDong || '관내'
const regInfo = getRegionByDong(dong)
const gu = regInfo?.sigungu ? `${regInfo.sigungu} ` : ''
const sub = props.subjectName || '수학'
const seed = Array.from(dong + sub).reduce((acc, ch, i) => acc + ch.charCodeAt(0) * (i + 2), 0)
const s1 = surnames[seed % surnames.length]
const s2 = surnames[(seed + 5) % surnames.length]
const s3 = surnames[(seed + 11) % surnames.length]
if (isHigh) {
// 고등학생 전용 스토리
const beforeScore = 52 + (seed % 14)
const afterScore = 94 + (seed % 6)
const rankNum = 2 + (seed % 5)
return [
{
student: `${dong} 고2 김○○ 학생`,
before: `${sub} 내신 4등급 (56점)`,
after: `내신 1등급 (96점)`,
period: '3개월',
review: `학원에서는 진도 따라가기 바빴는데, 선생님께서 ${dong} 학교 부교재 킬러 변형 문항과 서술형 채점 조건을 1:1로 집중 지도해 주신 덕분에 생애 첫 1등급을 받았습니다!`
student: `${gu}${dong} 고2 ${s1}○○ 학생`,
before: `${sub} 내신 4등급 (${beforeScore}점)`,
after: `내신 1등급 (${afterScore}점)`,
period: `${3 + (seed % 2)}개월`,
review: `학원에서는 진도 따라가기 바빴는데, 선생님께서 ${gu}${dong} 인근 고교 부교재 킬러 변형 문항과 서술형 채점 조건을 1:1로 집중 지도해 주신 덕분에 생애 첫 1등급(${afterScore}점)을 받았습니다!`
},
{
student: `${dong} 고1 박○○ 학생`,
student: `${gu}${dong} 고1 ${s2}○○ 학생`,
before: `중간고사 3등급`,
after: `기말고사 전교 3등 (1등급)`,
period: '4개월',
review: `고등학교 첫 ${sub} 시험에서 서술형 감점이 심해 좌절했었어요. 선생님과 1:1 오답 역추적 노트를 작성하며 출제 의도를 파악하는 훈련을 하니 시험 시간이 10분이나 남았습니다.`
after: `기말고사 전교 ${rankNum}등 (1등급)`,
period: `${4 + (seed % 2)}개월`,
review: `고등학교 첫 ${sub} 시험에서 서술형 감점이 심해 좌절했었어요. 선생님과 ${dong} 기출 1:1 오답 역추적 노트를 작성하며 출제 의도를 파악하는 훈련을 하니 시험 시간이 10분이나 남았습니다.`
},
{
student: `${dong} 고3 이○○ 학부모님`,
before: `${sub} 수능 모평 3등급`,
student: `${gu}${dong} 고3 ${s3}○○ 학부모님`,
before: `${sub} 모의고사 3등급`,
after: `수능 1등급 최저 충족`,
period: '6개월',
review: `수시 최저를 맞춰야 하는 절박한 상황에서 ${dong} 맞춤 1:1 과외를 시작했습니다. 취약한 킬러 유형만 집중 공략해 주셔서 원하는 명문대에 당당히 합격했습니다!`
review: `수시 최저를 맞춰야 하는 절박한 상황에서 ${gu}${dong} 방문 1:1 과외를 시작했습니다. 취약한 킬러 유형만 집중 공략해 주셔서 원하는 목표 대학에 당당히 합격했습니다!`
}
]
} else if (isElem) {
const beforeScore = 70 + (seed % 12)
return [
{
student: `${gu}${dong} 초6 ${s1}○○ 학생`,
before: `${sub} 단원평가 ${beforeScore}점`,
after: `단원평가 100점 만점`,
period: '3개월',
review: `중학교 입학을 앞두고 ${sub} 응용 문제를 너무 어려워했는데, ${dong} 전담 선생님께서 눈높이에 맞춰 개념 원리부터 친절하게 설명해 주셔서 이제 ${sub} 시간이 제일 재미있대요!`
},
{
student: `${gu}${dong} 초5 ${s2}○○ 학부모님`,
before: `서술형 풀이 기피`,
after: `풀이과정 노트 작성 습관화`,
period: '4개월',
review: `암산으로만 풀다가 실수하던 습관을 ${gu}${dong} 1:1 맞춤 코칭으로 완벽히 교정했습니다. 스스로 식을 세우고 검산하는 습관이 잡혀 중등 선행도 시작했습니다.`
},
{
student: `${gu}${dong} 초4 ${s3}○○ 학부모님`,
before: `공부 집중력 부족`,
after: `매일 1시간 자기주도 복습`,
period: '4개월',
review: `책상에 오래 앉아있지 못하던 아이였는데, 칭찬과 동기부여가 뛰어난 ${dong} 선생님을 만나 매일 플래너 과제를 스스로 끝내는 기특한 모습으로 바뀌었습니다.`
}
]
} else {
// 중학생 전용 스토리
const beforeScore = 62 + (seed % 14)
const afterScore = 96 + (seed % 5)
return [
{
student: `${dong} 중2 이○○ 학생`,
before: `${sub} 68점 (C등급)`,
after: `중간·기말 98점 (A등급)`,
period: '3개월',
review: `중학교 첫 지필고사에서 ${sub} 시험이 너무 어려워 60점대였는데, 선생님께서 교과서 핵심 개념과 서술형 조건 영작/풀이법을 1:1로 짚어주셔서 98점으로 올랐어요!`
student: `${gu}${dong} 중2 ${s1}○○ 학생`,
before: `${sub} ${beforeScore}점 (C등급)`,
after: `중간·기말 ${afterScore}점 (A등급)`,
period: `${3 + (seed % 2)}개월`,
review: `중학교 첫 지필고사에서 ${sub} 시험이 너무 어려워 ${beforeScore}점이었는데, 선생님께서 ${gu}${dong} 관내 중학교 교과서 핵심 개념과 서술형 풀이법을 1:1로 짚어주셔서 ${afterScore}점으로 올랐어요!`
},
{
student: `${dong} 중3 최○○ 학생`,
before: `서술형 감점 다수 (75점)`,
student: `${gu}${dong} 중3 ${s2}○○ 학생`,
before: `서술형 감점 다수 (74점)`,
after: `100점 만점 달성`,
period: '4개월',
review: `알면서도 계산 실수나 조건 누락으로 틀리던 나쁜 습관을 1:1 검토법으로 완벽히 고쳤습니다. 이번 기말고사에서 전교 1등 만점 맞고 고등 선행도 자신 있게 나가는 중입니다.`
review: `알면서도 계산 실수나 조건 누락으로 틀리던 나쁜 습관을 ${dong} 1:1 밀착 검토법으로 완벽히 고쳤습니다. 이번 기말고사에서 100점 만점 맞고 고등 선행도 자신 있게 나가는 중입니다.`
},
{
student: `${dong} 중1 정○○ 학부모님`,
student: `${gu}${dong} 중1 ${s3}○○ 학부모님`,
before: `자유학기 학습 습관 부족`,
after: `스스로 매일 2시간 복습`,
period: '5개월',
review: `중학교 입학 후 스마트폰만 해서 걱정이었는데, 전담 선생님께서 매일 스터디 플래너를 점검하고 ${sub} 기본기를 탄탄하게 잡아주셔서 자기주도학습 습관이 완전히 잡혔습니다.`
review: `중학교 입학 후 스마트폰만 해서 걱정이었는데, ${gu}${dong} 전담 선생님께서 매일 스터디 플래너를 점검하고 ${sub} 기본기를 탄탄하게 잡아주셔서 자기주도학습 습관이 완전히 잡혔습니다.`
}
]
}
+9 -9
View File
@@ -552,7 +552,7 @@
"sigungu": "안산시 단원구",
"dong": "고잔동",
"address": "경기도 안산시 단원구 광덕2로 222",
"detailAddress": "(고잔동, 고잔��등학교)",
"detailAddress": "(고잔동, 고잔고등학교)",
"type": "일반고",
"coedu": "남여공학",
"foundation": "공립",
@@ -1139,7 +1139,7 @@
"address": "경기도 하남시 신평로 25",
"detailAddress": ", 남한고등학교 (덕풍동)",
"type": "일반고",
"coedu": "��여공학",
"coedu": "남여공학",
"foundation": "공립",
"homepage": "https://namhan-h.goegh.kr/namhan-h/main.do"
},
@@ -1244,7 +1244,7 @@
"detailAddress": "(대부북동)",
"type": "일반고",
"coedu": "남여공학",
"foundation": "��립",
"foundation": "사립",
"homepage": "https://daebu-mh.goeas.kr/daebu-mh/main.do"
},
{
@@ -1604,7 +1604,7 @@
"region": "경기도",
"sigungu": "화성시",
"dong": "반송동",
"address": "경기도 화성시 ��탄나루로 27",
"address": "경기도 화성시 동탄나루로 27",
"detailAddress": ", 동탄국제고등학교 (반송동)",
"type": "특목고",
"coedu": "남여공학",
@@ -2555,7 +2555,7 @@
"dong": "본오동",
"address": "경기도 안산시 상록구 반석로4길 34",
"detailAddress": "(본오동, 상록고등학교)",
"type": "���반고",
"type": "일반고",
"coedu": "남여공학",
"foundation": "공립",
"homepage": "https://sangrok-h.goeas.kr"
@@ -2849,7 +2849,7 @@
{
"code": "7530475",
"name": "성안고등학교",
"region": "경기��",
"region": "경기도",
"sigungu": "안산시 상록구",
"dong": "사동",
"address": "경기도 안산시 상록구 용신로 170",
@@ -2998,7 +2998,7 @@
"address": "경기도 시흥시 수인로3287번길 26",
"detailAddress": "(은행동,소래고등학교)",
"type": "일반고",
"coedu": "���여공학",
"coedu": "남여공학",
"foundation": "공립",
"homepage": "https://sorae-h.goesh.kr/sorae-h/main.do"
},
@@ -3387,7 +3387,7 @@
"dong": "범박동",
"address": "경기도 부천시 은성로 172-20",
"detailAddress": ", 시온고등학교 (범박동)",
"type": "��반고",
"type": "일반고",
"coedu": "남여공학",
"foundation": "사립",
"homepage": "https://zion-h.goebc.kr/zion-h/main.do"
@@ -6063,7 +6063,7 @@
"region": "경기도",
"sigungu": "평택시",
"dong": "고덕면",
"address": "경기�� 평택시 고덕면 고덕북로 185",
"address": "경기도 평택시 고덕면 고덕북로 185",
"detailAddress": "(고덕면)",
"type": "특성화고",
"coedu": "남여공학",
+18 -18
View File
@@ -799,7 +799,7 @@
"sigungu": "안산시 단원구",
"dong": "고잔동",
"address": "경기도 안산시 단원구 광덕2로 222",
"detailAddress": "(고잔동, 고잔��등학교)",
"detailAddress": "(고잔동, 고잔고등학교)",
"type": "일반고",
"coedu": "남여공학",
"foundation": "공립",
@@ -2093,7 +2093,7 @@
"region": "경기도",
"sigungu": "화성시",
"dong": "반송동",
"address": "경기도 화성시 ��탄나루로 27",
"address": "경기도 화성시 동탄나루로 27",
"detailAddress": ", 동탄국제고등학교 (반송동)",
"type": "특목고",
"coedu": "남여공학",
@@ -2236,7 +2236,7 @@
"address": "경기도 하남시 신평로 25",
"detailAddress": ", 남한고등학교 (덕풍동)",
"type": "일반고",
"coedu": "��여공학",
"coedu": "남여공학",
"foundation": "공립",
"homepage": "https://namhan-h.goegh.kr/namhan-h/main.do"
},
@@ -2353,7 +2353,7 @@
"detailAddress": "(대부북동)",
"type": "일반고",
"coedu": "남여공학",
"foundation": "��립",
"foundation": "사립",
"homepage": "https://daebu-mh.goeas.kr/daebu-mh/main.do"
}
],
@@ -3570,7 +3570,7 @@
"dong": "범박동",
"address": "경기도 부천시 은성로 172-20",
"detailAddress": ", 시온고등학교 (범박동)",
"type": "��반고",
"type": "일반고",
"coedu": "남여공학",
"foundation": "사립",
"homepage": "https://zion-h.goebc.kr/zion-h/main.do"
@@ -4089,7 +4089,7 @@
"dong": "본오동",
"address": "경기도 안산시 상록구 반석로4길 34",
"detailAddress": "(본오동, 상록고등학교)",
"type": "���반고",
"type": "일반고",
"coedu": "남여공학",
"foundation": "공립",
"homepage": "https://sangrok-h.goeas.kr"
@@ -4329,7 +4329,7 @@
{
"code": "7530475",
"name": "성안고등학교",
"region": "경기��",
"region": "경기도",
"sigungu": "안산시 상록구",
"dong": "사동",
"address": "경기도 안산시 상록구 용신로 170",
@@ -4479,7 +4479,7 @@
"address": "경기도 시흥시 수인로3287번길 26",
"detailAddress": "(은행동,소래고등학교)",
"type": "일반고",
"coedu": "���여공학",
"coedu": "남여공학",
"foundation": "공립",
"homepage": "https://sorae-h.goesh.kr/sorae-h/main.do"
},
@@ -6717,7 +6717,7 @@
"region": "경기도",
"sigungu": "평택시",
"dong": "고덕면",
"address": "경기�� 평택시 고덕면 고덕북로 185",
"address": "경기도 평택시 고덕면 고덕북로 185",
"detailAddress": "(고덕면)",
"type": "특성화고",
"coedu": "남여공학",
@@ -7685,7 +7685,7 @@
"address": "경기도 하남시 신평로 25",
"detailAddress": ", 남한고등학교 (덕풍동)",
"type": "일반고",
"coedu": "��여공학",
"coedu": "남여공학",
"foundation": "공립",
"homepage": "https://namhan-h.goegh.kr/namhan-h/main.do"
},
@@ -8052,7 +8052,7 @@
"dong": "범박동",
"address": "경기도 부천시 은성로 172-20",
"detailAddress": ", 시온고등학교 (범박동)",
"type": "��반고",
"type": "일반고",
"coedu": "남여공학",
"foundation": "사립",
"homepage": "https://zion-h.goebc.kr/zion-h/main.do"
@@ -8567,7 +8567,7 @@
"dong": "본오동",
"address": "경기도 안산시 상록구 반석로4길 34",
"detailAddress": "(본오동, 상록고등학교)",
"type": "���반고",
"type": "일반고",
"coedu": "남여공학",
"foundation": "공립",
"homepage": "https://sangrok-h.goeas.kr"
@@ -8575,7 +8575,7 @@
{
"code": "7530475",
"name": "성안고등학교",
"region": "경기��",
"region": "경기도",
"sigungu": "안산시 상록구",
"dong": "사동",
"address": "경기도 안산시 상록구 용신로 170",
@@ -8931,7 +8931,7 @@
"region": "경기도",
"sigungu": "평택시",
"dong": "고덕면",
"address": "경기�� 평택시 고덕면 고덕북로 185",
"address": "경기도 평택시 고덕면 고덕북로 185",
"detailAddress": "(고덕면)",
"type": "특성화고",
"coedu": "남여공학",
@@ -9276,7 +9276,7 @@
"address": "경기도 시흥시 수인로3287번길 26",
"detailAddress": "(은행동,소래고등학교)",
"type": "일반고",
"coedu": "���여공학",
"coedu": "남여공학",
"foundation": "공립",
"homepage": "https://sorae-h.goesh.kr/sorae-h/main.do"
},
@@ -10001,7 +10001,7 @@
"sigungu": "안산시 단원구",
"dong": "고잔동",
"address": "경기도 안산시 단원구 광덕2로 222",
"detailAddress": "(고잔동, 고잔��등학교)",
"detailAddress": "(고잔동, 고잔고등학교)",
"type": "일반고",
"coedu": "남여공학",
"foundation": "공립",
@@ -10030,7 +10030,7 @@
"detailAddress": "(대부북동)",
"type": "일반고",
"coedu": "남여공학",
"foundation": "��립",
"foundation": "사립",
"homepage": "https://daebu-mh.goeas.kr/daebu-mh/main.do"
},
{
@@ -12303,7 +12303,7 @@
"region": "경기도",
"sigungu": "화성시",
"dong": "반송동",
"address": "경기도 화성시 ��탄나루로 27",
"address": "경기도 화성시 동탄나루로 27",
"detailAddress": ", 동탄국제고등학교 (반송동)",
"type": "특목고",
"coedu": "남여공학",
+5 -3
View File
@@ -9,6 +9,10 @@ export default defineNuxtConfig({
// SSR (Server-Side Rendering) 모드 강제 - 네이버 Yeti 봇 100% 대응
ssr: true,
routeRules: {
'/admin': { headers: { 'X-Robots-Tag': 'noindex, nofollow' } },
'/admin/**': { headers: { 'X-Robots-Tag': 'noindex, nofollow' } }
},
// Windows 경로 구분자 대응 및 Nitro 번들링 안정화
nitro: {
@@ -49,9 +53,7 @@ export default defineNuxtConfig({
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0' },
{ name: 'format-detection', content: 'telephone=no' },
// 네이버 서치어드바이저 사이트 소유확인 키 메타태그
{ name: 'naver-site-verification', content: process.env.NAVER_VERIFICATION_KEY || 'bfbfcb6ed8c107d05503feed914769a29b07cf06' }
{ name: 'format-detection', content: 'telephone=no' }
]
}
}
+4 -1
View File
@@ -1,7 +1,10 @@
User-agent: *
Allow: /
Disallow: /admin
User-agent: Yeti
Allow: /
Disallow: /admin
Sitemap: https://aeseo.shop/sitemap.xml
Sitemap: https://aeseo.xyz/sitemap.xml
Sitemap: https://aeseo.xyz/sitemap-images.xml
+109 -18
View File
@@ -1,33 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title><![CDATA[새솔 1:1 맞춤과외]]></title>
<title><![CDATA[새솔 1:1 맞춤과외 - 경기 중등 수학 과외 · 경기 초·중·고 1:1 맞춤 과외]]></title>
<link>https://aeseo.shop/</link>
<description><![CDATA[경기 초·중·고 1:1 맞춤 과외 및 방문·화상 시범수업 전문 교육 기관]]></description>
<description><![CDATA[경기 중등 수학 과외, 경기 중등 과외, 경기 초·중·고 국영수사과 1:1 방문·화상 맞춤 과외 및 무료 시범수업 전문 새솔 1:1 맞춤과외]]></description>
<language>ko</language>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate>
<lastBuildDate>Wed, 04 Nov 2026 00:47:43 GMT</lastBuildDate>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<lastBuildDate>Mon, 09 Nov 2026 01:00:00 GMT</lastBuildDate>
<atom:link href="https://aeseo.shop/rss.xml" rel="self" type="application/rss+xml"/>
<item>
<title><![CDATA[경기 초·중·고 1:1 맞춤 과외 매칭]]></title>
<title><![CDATA[경기 중등 수학 과외 · 경기 중등 과외 | 초·중·고 1:1 맞춤 과외 새솔 1:1 맞춤과외]]></title>
<link>https://aeseo.shop/</link>
<description><![CDATA[과외 비용·시세 투명 비교부터 무료 시범수업 신청까지]]></description>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate>
<description><![CDATA[경기 전 지역 초·중·고 1:1 방문·화상 맞춤 과외 비용 비교 및 무료 시범수업 신청]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/</guid>
</item>
<item>
<title><![CDATA[수내동 중학생 수학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EC%88%98%EB%82%B4%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
<description><![CDATA[분당 수내동 인근 학교별 내신 기출 분석]]></description>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate>
<guid>https://aeseo.shop/%EC%88%98%EB%82%B4%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
<title><![CDATA[경기 초등 수학 과외 · 가평읍 초등학생 수학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EA%B0%80%ED%8F%89%EC%9D%8D/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
<description><![CDATA[경기 초등 수학 과외 및 가평읍 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EA%B0%80%ED%8F%89%EC%9D%8D/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
</item>
<item>
<title><![CDATA[마두동 고등학생 영어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EB%A7%88%EB%91%90%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
<description><![CDATA[일산 학군 내신 서술형 대비]]></description>
<pubDate>Wed, 04 Nov 2026 00:47:43 GMT</pubDate>
<guid>https://aeseo.shop/%EB%A7%88%EB%91%90%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
<title><![CDATA[경기 중등 영어 과외 · 설악면 중학생 영어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EC%84%A4%EC%95%85%EB%A9%B4/%EC%A4%91%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
<description><![CDATA[경기 중등 영어 과외 및 설악면 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EC%84%A4%EC%95%85%EB%A9%B4/%EC%A4%91%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[경기 고등 국어 과외 · 조종면 고등학생 국어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EC%A1%B0%EC%A2%85%EB%A9%B4/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
<description><![CDATA[경기 고등 국어 과외 및 조종면 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EC%A1%B0%EC%A2%85%EB%A9%B4/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[경기 초등 과학 과외 · 청평면 초등학생 과학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EC%B2%AD%ED%8F%89%EB%A9%B4/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
<description><![CDATA[경기 초등 과학 과외 및 청평면 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EC%B2%AD%ED%8F%89%EB%A9%B4/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
</item>
<item>
<title><![CDATA[경기 중등 사회 과외 · 고양동 중학생 사회과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EA%B3%A0%EC%96%91%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
<description><![CDATA[경기 중등 사회 과외 및 고양동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EA%B3%A0%EC%96%91%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
</item>
<item>
<title><![CDATA[경기 고등 수학 과외 · 관산동 고등학생 수학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EA%B4%80%EC%82%B0%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
<description><![CDATA[경기 고등 수학 과외 및 관산동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EA%B4%80%EC%82%B0%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
</item>
<item>
<title><![CDATA[경기 초등 영어 과외 · 도내동 초등학생 영어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EB%8F%84%EB%82%B4%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
<description><![CDATA[경기 초등 영어 과외 및 도내동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EB%8F%84%EB%82%B4%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[경기 중등 국어 과외 · 동산동 중학생 국어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EB%8F%99%EC%82%B0%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
<description><![CDATA[경기 중등 국어 과외 및 동산동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EB%8F%99%EC%82%B0%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[경기 고등 과학 과외 · 삼송동 고등학생 과학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EC%82%BC%EC%86%A1%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
<description><![CDATA[경기 고등 과학 과외 및 삼송동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EC%82%BC%EC%86%A1%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
</item>
<item>
<title><![CDATA[경기 초등 사회 과외 · 성사동 초등학생 사회과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EC%84%B1%EC%82%AC%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
<description><![CDATA[경기 초등 사회 과외 및 성사동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EC%84%B1%EC%82%AC%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
</item>
<item>
<title><![CDATA[경기 중등 수학 과외 · 신원동 중학생 수학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EC%8B%A0%EC%9B%90%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</link>
<description><![CDATA[경기 중등 수학 과외 및 신원동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EC%8B%A0%EC%9B%90%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EC%88%98%ED%95%99</guid>
</item>
<item>
<title><![CDATA[경기 고등 영어 과외 · 주교동 고등학생 영어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%EC%A3%BC%EA%B5%90%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</link>
<description><![CDATA[경기 고등 영어 과외 및 주교동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%EC%A3%BC%EA%B5%90%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%98%81%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[경기 초등 국어 과외 · 토당동 초등학생 국어과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%ED%86%A0%EB%8B%B9%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</link>
<description><![CDATA[경기 초등 국어 과외 및 토당동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%ED%86%A0%EB%8B%B9%EB%8F%99/%EC%B4%88%EB%93%B1%ED%95%99%EC%83%9D/%EA%B5%AD%EC%96%B4</guid>
</item>
<item>
<title><![CDATA[경기 중등 과학 과외 · 행신동 중학생 과학과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%ED%96%89%EC%8B%A0%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</link>
<description><![CDATA[경기 중등 과학 과외 및 행신동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%ED%96%89%EC%8B%A0%EB%8F%99/%EC%A4%91%ED%95%99%EC%83%9D/%EA%B3%BC%ED%95%99</guid>
</item>
<item>
<title><![CDATA[경기 고등 사회 과외 · 향동동 고등학생 사회과외 1:1 맞춤 수업]]></title>
<link>https://aeseo.shop/%ED%96%A5%EB%8F%99%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</link>
<description><![CDATA[경기 고등 사회 과외 및 향동동 관내 학교 내신 기출 분석, 1:1 방문·화상 무료 시범수업 안내]]></description>
<pubDate>Mon, 09 Nov 2026 01:00:00 GMT</pubDate>
<guid>https://aeseo.shop/%ED%96%A5%EB%8F%99%EB%8F%99/%EA%B3%A0%EB%93%B1%ED%95%99%EC%83%9D/%EC%82%AC%ED%9A%8C</guid>
</item>
</channel>
</rss>
</rss>
File diff suppressed because it is too large Load Diff
+5730 -7556
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,51 @@
import regionsData from '../../data/regions.json'
export default defineEventHandler((event) => {
const config = useRuntimeConfig()
const baseUrl = config.public.siteDomain || 'https://aeseo.shop'
const list = regionsData.list as Array<{ sido: string, sigungu: string, dong: string }>
const uniqueDongs = Array.from(new Set(list.map(r => r.dong).filter(Boolean)))
const today = new Date().toISOString().split('T')[0]
const subjects = ['수학', '영어', '국어', '과학', '사회']
const grades = ['초등학생', '중학생', '고등학생']
const subjectImages: Record<string, string> = {
'수학': `${baseUrl}/img/math-tutoring.png`,
'영어': `${baseUrl}/img/english-tutoring.png`,
'국어': `${baseUrl}/img/korean-tutoring.png`,
'과학': `${baseUrl}/img/science-tutoring.png`,
'사회': `${baseUrl}/img/social-studies-tutoring.png`
}
let xml = `<?xml version="1.0" encoding="UTF-8"?>\n`
xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">\n`
// 메인
xml += ` <url>\n <loc>${baseUrl}/</loc>\n <lastmod>${today}</lastmod>\n <changefreq>daily</changefreq>\n <priority>1.0</priority>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/og-main.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/english-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/korean-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/science-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/social-studies-tutoring.png</image:loc></image:image>\n`
xml += ` </url>\n`
// 중복 제거된 동별 주요 과목 및 학년 URL 생성 (슬래시 라우터 구조 통일)
uniqueDongs.forEach(dong => {
// 1. 동 대표
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.8</priority>\n <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n </url>\n`
// 2. 동/학년/과목 조합
grades.forEach(g => {
subjects.forEach(sub => {
const imgLoc = subjectImages[sub] || `${baseUrl}/img/math-tutoring.png`
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.7</priority>\n <image:image><image:loc>${imgLoc}</image:loc></image:image>\n </url>\n`
})
})
})
xml += `</urlset>`
setHeader(event, 'content-type', 'application/xml; charset=utf-8')
return xml
})
+16 -29
View File
@@ -4,8 +4,11 @@ export default defineEventHandler((event) => {
const config = useRuntimeConfig()
const baseUrl = config.public.siteDomain || 'https://aeseo.shop'
const list = regionsData.list as Array<{ sido: string, sigungu: string, dong: string }>
const uniqueDongs = Array.from(new Set(list.map(r => r.dong).filter(Boolean)))
const today = new Date().toISOString().split('T')[0]
const subjects = ['수학', '영어', '국어', '과학', '사회']
const grades = ['초등학생', '중학생', '고등학생']
const subjectImages: Record<string, string> = {
'수학': `${baseUrl}/img/math-tutoring.png`,
'영어': `${baseUrl}/img/english-tutoring.png`,
@@ -13,46 +16,30 @@ export default defineEventHandler((event) => {
'과학': `${baseUrl}/img/science-tutoring.png`,
'사회': `${baseUrl}/img/social-studies-tutoring.png`
}
const subjects = ['수학', '영어', '국어', '과학', '사회']
const grades = ['초등학생', '중학생', '고등학생']
let xml = `<?xml version="1.0" encoding="UTF-8"?>\n`
xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">\n`
// 메인 (대표 이미지 + 5대 과목 이미지 모두 포함)
xml += ` <url>\n`
xml += ` <loc>${baseUrl}/</loc>\n`
xml += ` <lastmod>${today}</lastmod>\n`
xml += ` <changefreq>daily</changefreq>\n`
xml += ` <priority>1.0</priority>\n`
// 메인
xml += ` <url>\n <loc>${baseUrl}/</loc>\n <lastmod>${today}</lastmod>\n <changefreq>daily</changefreq>\n <priority>1.0</priority>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/og-main.png</image:loc></image:image>\n`
subjects.forEach(sub => {
xml += ` <image:image><image:loc>${subjectImages[sub]}</image:loc></image:image>\n`
})
xml += ` <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/english-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/korean-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/science-tutoring.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/social-studies-tutoring.png</image:loc></image:image>\n`
xml += ` </url>\n`
// 동별 주요 과목 및 학년 URL 생성
list.forEach(r => {
// 중복 제거된 동별 주요 과목 및 학년 URL 생성 (슬래시 라우터 구조 통일)
uniqueDongs.forEach(dong => {
// 1. 동 대표
xml += ` <url>\n`
xml += ` <loc>${baseUrl}/${encodeURIComponent(r.dong)}</loc>\n`
xml += ` <lastmod>${today}</lastmod>\n`
xml += ` <changefreq>weekly</changefreq>\n`
xml += ` <priority>0.8</priority>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/og-main.png</image:loc></image:image>\n`
xml += ` <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n`
xml += ` </url>\n`
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.8</priority>\n <image:image><image:loc>${baseUrl}/img/math-tutoring.png</image:loc></image:image>\n </url>\n`
// 2. 동/학년/과목 대표 조합
// 2. 동/학년/과목 조합
grades.forEach(g => {
subjects.forEach(sub => {
xml += ` <url>\n`
xml += ` <loc>${baseUrl}/${encodeURIComponent(r.dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n`
xml += ` <lastmod>${today}</lastmod>\n`
xml += ` <changefreq>weekly</changefreq>\n`
xml += ` <priority>0.7</priority>\n`
xml += ` <image:image><image:loc>${subjectImages[sub]}</image:loc></image:image>\n`
xml += ` </url>\n`
const imgLoc = subjectImages[sub] || `${baseUrl}/img/math-tutoring.png`
xml += ` <url>\n <loc>${baseUrl}/${encodeURIComponent(dong)}/${encodeURIComponent(g)}/${encodeURIComponent(sub)}</loc>\n <lastmod>${today}</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.7</priority>\n <image:image><image:loc>${imgLoc}</image:loc></image:image>\n </url>\n`
})
})
})
+13 -7
View File
@@ -113,30 +113,35 @@ export function parseRouteSlug(slugInput: string | string[]): ParsedRouteParams
} else if (typeof slugInput === 'string' && slugInput.trim()) {
tokens = decodeURIComponent(slugInput).split(/[-_\s/]+/)
}
tokens = tokens.map(t => t.trim()).filter(Boolean)
tokens = tokens.map(t => t.trim()).filter(t => Boolean(t) && !/^index\.html?$/i.test(t))
let matchedRegion: RegionInfo | null = null
let matchedTokenIdx = -1
let detectedGrade = '중학생'
let detectedGradeShort = '중등'
let detectedSubjectKey = 'english'
let detectedSubjectName = '영어'
let detectedSubjectKey = 'math'
let detectedSubjectName = '수학'
let detectedTerm = '과외'
// 1. 토큰들 중 동 이름 매칭
for (const token of tokens) {
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i]
const reg = getRegionByDong(token)
if (reg) {
matchedRegion = reg
matchedTokenIdx = i
break
}
}
// 2. 동 이름이 토큰에 없으면 전체 리스트에서 부분 일치 검색
if (!matchedRegion && tokens.length > 0) {
for (const token of tokens) {
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i]
const found = (regionsData.list as RegionInfo[]).find(r => token.includes(r.dong) || (token.length >= 2 && r.dong.includes(token)))
if (found) {
matchedRegion = found
matchedTokenIdx = i
break
}
}
@@ -152,9 +157,10 @@ export function parseRouteSlug(slugInput: string | string[]): ParsedRouteParams
}
}
// 3. 학년 추출
// 3. 학년 추출 (동 이름 토큰을 제외한 나머지 토큰에서만 검색하여 '고등동', '문학동' 등 별칭 충돌 원천 차단)
let hasExplicitGrade = false
const fullText = tokens.join(' ')
const remainingTokens = tokens.filter((_, idx) => idx !== matchedTokenIdx)
const fullText = remainingTokens.join(' ')
for (const g of GRADES_LIST) {
if (g.aliases.some(a => fullText.includes(a))) {
detectedGrade = g.name
+10 -5
View File
@@ -1,5 +1,5 @@
import type { ParsedRouteParams } from './dataHelper'
import { getShortRegionName } from './dataHelper'
import { getShortRegionName, getShortSido } from './dataHelper'
const SUBJECT_IMAGE_MAP: Record<string, string> = {
math: '/img/math-tutoring.png',
@@ -22,18 +22,23 @@ const GRADE_LIST = ['초등학생', '중학생', '고등학생']
export function generateSeoMeta(parsed: ParsedRouteParams, siteName = '새솔 1:1 맞춤과외', siteDomain = 'https://aeseo.shop') {
const { sido, sigungu, dong, shortRegionName, grade, subjectKey, subjectName, isRegionOnly } = parsed
const shortSido = (sido || '').replace(/특별시|광역시|특별자치시|도/g, '').trim() || '경기'
const shortSido = getShortSido(sido) || '경기'
const gradeShort = parsed.gradeShort || (grade === '초등학생' ? '초등' : grade === '고등학생' ? '고등' : '중등')
const regionTitle = shortRegionName || getShortRegionName(sido, dong)
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 맞춤 과외와 검증된 전문 선생님 무료 시범수업, 과외비 비교를 확인하세요.`
: `${shortSido} ${gradeShort} ${subjectName} 과외, ${shortSido} ${gradeShort} 과외, ${dong} ${gradeShort} ${subjectName} 과외 전문 ${siteName}! ${regionTitle} ${grade} ${subjectName} 1:1 방문·화상 맞춤 수업으로 내신·수능 성적 향상을 책임집니다. 과외비 투명 공개 및 무료 시범수업 신청하세요.`
? `${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