인기 페이지 통계와 추천 사이트 메타데이터 추가 v1.5.9

This commit is contained in:
2026-05-27 10:34:07 +09:00
parent d7a3149ea1
commit fd9416c0e4
22 changed files with 596 additions and 94 deletions

View File

@@ -45,6 +45,9 @@ let currentPath = ''
/** @type {string} 현재 추적 게시물 slug */
let currentPostSlug = ''
/** @type {string} 현재 추적 페이지 slug */
let currentPageSlug = ''
/** @type {number} 현재 페이지 체류 시작 시각 */
let pageStartedAt = 0
@@ -85,6 +88,21 @@ const extractPostSlugFromRoute = (route) => {
return match?.[1] ? decodeURIComponent(match[1]).trim() : ''
}
/**
* 고정 페이지 경로에서 slug를 추출한다.
* @param {import('vue-router').RouteLocationNormalizedLoaded} route - 현재 라우트
* @returns {string} slug 또는 빈 문자열
*/
const extractPageSlugFromRoute = (route) => {
const paramSlug = String(route.params?.slug || '').trim()
if (paramSlug && String(route.path || '').startsWith('/pages/')) {
return paramSlug
}
const match = String(route.path || '').match(/^\/pages\/([^/?#]+)/)
return match?.[1] ? decodeURIComponent(match[1]).trim() : ''
}
/**
* 문서 스크롤 진행 비율(0~1)을 반환한다.
* @returns {number} 스크롤 비율
@@ -148,6 +166,7 @@ const clearPageTracking = () => {
readStartedAt = 0
currentPath = ''
currentPostSlug = ''
currentPageSlug = ''
pageStartedAt = 0
maxScrollRatio = 0
}
@@ -182,26 +201,28 @@ const sendAnalyticsPayload = (endpoint, payload) => {
/**
* pageview 이벤트를 전송한다.
* @param {{ path: string, postSlug?: string, read?: boolean }} payload - 전송 본문
* @param {{ path: string, postSlug?: string, pageSlug?: string, read?: boolean }} payload - 전송 본문
* @returns {void}
*/
const sendPageviewEvent = (payload) => {
sendAnalyticsPayload('/api/analytics/pageview', {
path: payload.path,
postSlug: payload.postSlug || '',
pageSlug: payload.pageSlug || '',
read: Boolean(payload.read)
})
}
/**
* heartbeat 이벤트를 전송한다.
* @param {{ path: string, postSlug?: string, durationSeconds: number, maxScrollRatio: number }} payload - 전송 본문
* @param {{ path: string, postSlug?: string, pageSlug?: string, durationSeconds: number, maxScrollRatio: number }} payload - 전송 본문
* @returns {void}
*/
const sendHeartbeatEvent = (payload) => {
sendAnalyticsPayload('/api/analytics/heartbeat', {
path: payload.path,
postSlug: payload.postSlug || '',
pageSlug: payload.pageSlug || '',
clientSessionId: getClientSessionId(),
durationSeconds: payload.durationSeconds,
maxScrollRatio: payload.maxScrollRatio
@@ -222,6 +243,7 @@ const sendCurrentHeartbeat = () => {
sendHeartbeatEvent({
path: currentPath,
postSlug: currentPostSlug,
pageSlug: currentPageSlug,
durationSeconds: getCurrentDurationSeconds(),
maxScrollRatio
})
@@ -308,13 +330,15 @@ const trackRouteAnalytics = (route) => {
sendCurrentHeartbeat()
const postSlug = extractPostSlugFromRoute(route)
const viewKey = `view:${path}:${postSlug}`
const pageSlug = postSlug ? '' : extractPageSlugFromRoute(route)
const viewKey = `view:${path}:${postSlug}:${pageSlug}`
if (!sentViewKeys.has(viewKey)) {
sentViewKeys.add(viewKey)
sendPageviewEvent({
path,
postSlug,
pageSlug,
read: false
})
}
@@ -323,6 +347,7 @@ const trackRouteAnalytics = (route) => {
currentPath = path
currentPostSlug = postSlug
currentPageSlug = pageSlug
pageStartedAt = Date.now()
maxScrollRatio = getDocumentScrollRatio()