feat: Schema.org 구조화된 데이터 추가 및 페이지별 BreadcrumbList 생성
This commit is contained in:
94
app/app.vue
94
app/app.vue
@@ -2,6 +2,8 @@
|
||||
import { useNuxtApp } from 'nuxt/app'
|
||||
|
||||
const nuxtApp = useNuxtApp()
|
||||
const route = useRoute()
|
||||
const requestURL = useRequestURL()
|
||||
const { locale } = useI18n()
|
||||
const gameDataStore = useGameDataStore()
|
||||
const modalStore = useModalStore()
|
||||
@@ -122,6 +124,97 @@ const setupGameHead = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// Schema.org 구조화된 데이터 설정
|
||||
const setupStructuredData = () => {
|
||||
if (!gameName.value) return
|
||||
|
||||
try {
|
||||
// SEO용 URL은 설정 도메인이 아닌 실제 요청 호스트 기준 (SSR/CSR 모두 useRequestURL)
|
||||
const baseUrl = requestURL.origin
|
||||
const currentLocale = locale.value ?? defaultLangCode.value ?? 'ko'
|
||||
|
||||
// Organization Schema
|
||||
const organizationSchema: Record<string, any> = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Organization',
|
||||
name: gameName.value,
|
||||
url: baseUrl,
|
||||
}
|
||||
|
||||
// 로고 이미지 추가 (favicon 중 PNG 우선)
|
||||
if (faviconJson.value) {
|
||||
const logoUrl = faviconJson.value[2] || faviconJson.value[0]
|
||||
if (logoUrl) {
|
||||
organizationSchema.logo = formatPathHost(logoUrl)
|
||||
}
|
||||
}
|
||||
|
||||
// SNS 링크 추가
|
||||
const snsData = gameDataStore.snsJson
|
||||
if (snsData) {
|
||||
const sameAs: string[] = []
|
||||
if (snsData.youtube?.use_yn === 1 && snsData.youtube?.url) {
|
||||
sameAs.push(snsData.youtube.url)
|
||||
}
|
||||
if (snsData.twitter?.use_yn === 1 && snsData.twitter?.url) {
|
||||
sameAs.push(snsData.twitter.url)
|
||||
}
|
||||
if (snsData.facebook?.use_yn === 1 && snsData.facebook?.url) {
|
||||
sameAs.push(snsData.facebook.url)
|
||||
}
|
||||
if (snsData.discord?.use_yn === 1 && snsData.discord?.url) {
|
||||
sameAs.push(snsData.discord.url)
|
||||
}
|
||||
if (snsData.instagram?.use_yn === 1 && snsData.instagram?.url) {
|
||||
sameAs.push(snsData.instagram.url)
|
||||
}
|
||||
if (snsData.tiktok?.use_yn === 1 && snsData.tiktok?.url) {
|
||||
sameAs.push(snsData.tiktok.url)
|
||||
}
|
||||
|
||||
if (sameAs.length > 0) {
|
||||
organizationSchema.sameAs = sameAs
|
||||
}
|
||||
}
|
||||
|
||||
// WebSite Schema - 사이트 정보
|
||||
const websiteSchema: Record<string, any> = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebSite',
|
||||
name: gameName.value,
|
||||
url: baseUrl,
|
||||
inLanguage: currentLocale,
|
||||
}
|
||||
|
||||
// 설명 추가 (있는 경우)
|
||||
if (gameMetaTag.value?.page_desc) {
|
||||
websiteSchema.description = gameMetaTag.value.page_desc
|
||||
}
|
||||
|
||||
useHead({
|
||||
script: [
|
||||
{
|
||||
type: 'application/ld+json',
|
||||
innerHTML: JSON.stringify(organizationSchema),
|
||||
},
|
||||
{
|
||||
type: 'application/ld+json',
|
||||
innerHTML: JSON.stringify(websiteSchema),
|
||||
},
|
||||
],
|
||||
link: [
|
||||
{
|
||||
rel: 'canonical',
|
||||
href: `${baseUrl}${route.path}`,
|
||||
},
|
||||
],
|
||||
})
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('[setupStructuredData] Failed to setup schema:', error)
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.server) {
|
||||
const gameData = nuxtApp.ssrContext?.event?.context?.gameData
|
||||
if (gameData) {
|
||||
@@ -130,6 +223,7 @@ if (import.meta.server) {
|
||||
}
|
||||
|
||||
setupGameHead()
|
||||
setupStructuredData()
|
||||
|
||||
let rafId: number | null = null
|
||||
onMounted(() => {
|
||||
|
||||
Reference in New Issue
Block a user