Merge branch 'feature/20260327_gil_nitroLog' into feature/20260331_all
This commit is contained in:
8
.cursorrules
Normal file
8
.cursorrules
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# 커밋 메시지 (Generate Commit Message 포함)
|
||||||
|
|
||||||
|
커밋 메시지는 **반드시 한글**로 작성한다.
|
||||||
|
|
||||||
|
- 형식: `feat: 내용`, `refactor: 내용`, `test: 내용` 등
|
||||||
|
- 내용은 한글로, 제목 한 줄, 변경 의도가 드러나게 간결하게
|
||||||
|
|
||||||
|
예: `feat: 회원 가입 폼 유효성 검사 추가`, `refactor: 결제 모듈 상태 관리 로직 분리`
|
||||||
94
app/app.vue
94
app/app.vue
@@ -1,4 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
const route = useRoute()
|
||||||
|
const requestURL = useRequestURL()
|
||||||
const { locale } = useI18n()
|
const { locale } = useI18n()
|
||||||
const gameDataStore = useGameDataStore()
|
const gameDataStore = useGameDataStore()
|
||||||
const modalStore = useModalStore()
|
const modalStore = useModalStore()
|
||||||
@@ -91,7 +93,99 @@ 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setupGameHead()
|
setupGameHead()
|
||||||
|
setupStructuredData()
|
||||||
|
|
||||||
let rafId: number | null = null
|
let rafId: number | null = null
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -61,6 +61,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
// SEO: Prevent search engine indexing of error pages
|
||||||
|
useSeoMeta({
|
||||||
|
robots: 'noindex, nofollow',
|
||||||
|
})
|
||||||
|
|
||||||
interface ErrorProps {
|
interface ErrorProps {
|
||||||
error?: {
|
error?: {
|
||||||
statusCode?: number
|
statusCode?: number
|
||||||
|
|||||||
@@ -119,6 +119,11 @@ import { globalDateFormat } from '@seed-next/date'
|
|||||||
import AtomsIconsPlayRoundFill from '#layers/components/atoms/icons/PlayRoundFill.vue'
|
import AtomsIconsPlayRoundFill from '#layers/components/atoms/icons/PlayRoundFill.vue'
|
||||||
import type { PlatformTransformType } from '#layers/types/api/gameData'
|
import type { PlatformTransformType } from '#layers/types/api/gameData'
|
||||||
|
|
||||||
|
// SEO: Prevent search engine indexing of inspection pages
|
||||||
|
useSeoMeta({
|
||||||
|
robots: 'noindex, nofollow',
|
||||||
|
})
|
||||||
|
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
const stoveApiUrl = config.public.stoveApiUrl as string
|
const stoveApiUrl = config.public.stoveApiUrl as string
|
||||||
|
|
||||||
|
|||||||
221
layers/composables/useSchema.ts
Normal file
221
layers/composables/useSchema.ts
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
/**
|
||||||
|
* Schema.org 구조화된 데이터 유틸리티
|
||||||
|
*
|
||||||
|
* Schema.org 마크업을 쉽게 추가하고 관리하기 위한 composable
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const { addSchema, addVideoSchema, addImageGallerySchema } = useSchema()
|
||||||
|
*
|
||||||
|
* // 커스텀 스키마 추가
|
||||||
|
* addSchema({
|
||||||
|
* '@context': 'https://schema.org',
|
||||||
|
* '@type': 'Article',
|
||||||
|
* headline: 'My Article'
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* // 비디오 스키마 추가
|
||||||
|
* addVideoSchema({
|
||||||
|
* name: 'Video Title',
|
||||||
|
* description: 'Video Description',
|
||||||
|
* thumbnailUrl: 'https://example.com/thumb.jpg',
|
||||||
|
* uploadDate: '2025-01-01',
|
||||||
|
* contentUrl: 'https://youtube.com/watch?v=xxx'
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const useSchema = () => {
|
||||||
|
const gameDataStore = useGameDataStore()
|
||||||
|
const { gameName } = storeToRefs(gameDataStore)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 커스텀 Schema.org JSON-LD를 head에 추가
|
||||||
|
*/
|
||||||
|
const addSchema = (schema: Record<string, any>) => {
|
||||||
|
useHead({
|
||||||
|
script: [
|
||||||
|
{
|
||||||
|
type: 'application/ld+json',
|
||||||
|
innerHTML: JSON.stringify(schema),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VideoObject Schema 생성 및 추가
|
||||||
|
*/
|
||||||
|
const addVideoSchema = (options: {
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
thumbnailUrl: string
|
||||||
|
uploadDate: string
|
||||||
|
contentUrl: string
|
||||||
|
embedUrl?: string
|
||||||
|
}) => {
|
||||||
|
const schema = {
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'VideoObject',
|
||||||
|
name: options.name,
|
||||||
|
description: options.description || options.name,
|
||||||
|
thumbnailUrl: options.thumbnailUrl,
|
||||||
|
uploadDate: options.uploadDate,
|
||||||
|
contentUrl: options.contentUrl,
|
||||||
|
embedUrl: options.embedUrl || options.contentUrl,
|
||||||
|
publisher: {
|
||||||
|
'@type': 'Organization',
|
||||||
|
name: gameName.value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
addSchema(schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ImageGallery Schema 생성 및 추가
|
||||||
|
*/
|
||||||
|
const addImageGallerySchema = (
|
||||||
|
images: Array<{
|
||||||
|
contentUrl: string
|
||||||
|
caption?: string
|
||||||
|
}>
|
||||||
|
) => {
|
||||||
|
const schema = {
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'ImageGallery',
|
||||||
|
image: images.map(img => ({
|
||||||
|
'@type': 'ImageObject',
|
||||||
|
contentUrl: img.contentUrl,
|
||||||
|
caption: img.caption || '',
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
|
||||||
|
addSchema(schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SoftwareApplication Schema 생성 및 추가
|
||||||
|
*/
|
||||||
|
const addSoftwareApplicationSchema = (options: {
|
||||||
|
name: string
|
||||||
|
operatingSystem: string
|
||||||
|
description?: string
|
||||||
|
price?: string
|
||||||
|
priceCurrency?: string
|
||||||
|
}) => {
|
||||||
|
const schema: Record<string, any> = {
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'SoftwareApplication',
|
||||||
|
name: options.name,
|
||||||
|
operatingSystem: options.operatingSystem,
|
||||||
|
applicationCategory: 'GameApplication',
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.description) {
|
||||||
|
schema.description = options.description
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.price !== undefined) {
|
||||||
|
schema.offers = {
|
||||||
|
'@type': 'Offer',
|
||||||
|
price: options.price,
|
||||||
|
priceCurrency: options.priceCurrency || 'USD',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addSchema(schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event Schema 생성 및 추가
|
||||||
|
*/
|
||||||
|
const addEventSchema = (options: {
|
||||||
|
name: string
|
||||||
|
startDate: string
|
||||||
|
endDate?: string
|
||||||
|
eventStatus?:
|
||||||
|
| 'EventScheduled'
|
||||||
|
| 'EventCancelled'
|
||||||
|
| 'EventMovedOnline'
|
||||||
|
| 'EventPostponed'
|
||||||
|
| 'EventRescheduled'
|
||||||
|
description?: string
|
||||||
|
image?: string
|
||||||
|
location?: string
|
||||||
|
}) => {
|
||||||
|
const schema: Record<string, any> = {
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'Event',
|
||||||
|
name: options.name,
|
||||||
|
startDate: options.startDate,
|
||||||
|
eventStatus: `https://schema.org/${options.eventStatus || 'EventScheduled'}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.endDate) {
|
||||||
|
schema.endDate = options.endDate
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.description) {
|
||||||
|
schema.description = options.description
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.image) {
|
||||||
|
schema.image = options.image
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.location) {
|
||||||
|
schema.location = {
|
||||||
|
'@type': 'VirtualLocation',
|
||||||
|
url: options.location,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addSchema(schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Article Schema 생성 및 추가
|
||||||
|
*/
|
||||||
|
const addArticleSchema = (options: {
|
||||||
|
headline: string
|
||||||
|
datePublished: string
|
||||||
|
dateModified?: string
|
||||||
|
author?: string
|
||||||
|
description?: string
|
||||||
|
image?: string
|
||||||
|
}) => {
|
||||||
|
const schema: Record<string, any> = {
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'Article',
|
||||||
|
headline: options.headline,
|
||||||
|
datePublished: options.datePublished,
|
||||||
|
author: {
|
||||||
|
'@type': 'Organization',
|
||||||
|
name: options.author || gameName.value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.dateModified) {
|
||||||
|
schema.dateModified = options.dateModified
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.description) {
|
||||||
|
schema.description = options.description
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.image) {
|
||||||
|
schema.image = options.image
|
||||||
|
}
|
||||||
|
|
||||||
|
addSchema(schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
addSchema,
|
||||||
|
addVideoSchema,
|
||||||
|
addImageGallerySchema,
|
||||||
|
addSoftwareApplicationSchema,
|
||||||
|
addEventSchema,
|
||||||
|
addArticleSchema,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,70 @@
|
|||||||
<script setup lang="ts"></script>
|
<script setup lang="ts">
|
||||||
|
const route = useRoute()
|
||||||
|
const { locale } = useI18n()
|
||||||
|
const gameDataStore = useGameDataStore()
|
||||||
|
const { gameName } = storeToRefs(gameDataStore)
|
||||||
|
const requestURL = useRequestURL()
|
||||||
|
|
||||||
|
// BreadcrumbList Schema 생성
|
||||||
|
const breadcrumbSchema = computed(() => {
|
||||||
|
if (!gameName.value) return null
|
||||||
|
|
||||||
|
const baseUrl = requestURL.origin
|
||||||
|
console.log("🚀 ~ baseUrl:", baseUrl)
|
||||||
|
const pathSegments = route.path.split('/').filter(Boolean)
|
||||||
|
const currentLocale = pathSegments[0] || locale.value
|
||||||
|
|
||||||
|
const itemListElement: Array<{
|
||||||
|
'@type': string
|
||||||
|
position: number
|
||||||
|
name: string
|
||||||
|
item: string
|
||||||
|
}> = [
|
||||||
|
{
|
||||||
|
'@type': 'ListItem',
|
||||||
|
position: 1,
|
||||||
|
name: gameName.value,
|
||||||
|
item: `${baseUrl}/${currentLocale}`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
// 로케일 이후의 경로 세그먼트들을 breadcrumb에 추가
|
||||||
|
pathSegments.slice(1).forEach((segment, index) => {
|
||||||
|
const path = `/${pathSegments.slice(0, index + 2).join('/')}`
|
||||||
|
const displayName = segment
|
||||||
|
.replace(/-/g, ' ')
|
||||||
|
.replace(/\b\w/g, l => l.toUpperCase())
|
||||||
|
|
||||||
|
itemListElement.push({
|
||||||
|
'@type': 'ListItem',
|
||||||
|
position: index + 2,
|
||||||
|
name: displayName,
|
||||||
|
item: `${baseUrl}${path}`,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log("🚀 ~ itemListElement:", itemListElement)
|
||||||
|
return {
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'BreadcrumbList',
|
||||||
|
itemListElement,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Schema를 head에 추가
|
||||||
|
watchEffect(() => {
|
||||||
|
if (breadcrumbSchema.value) {
|
||||||
|
useHead({
|
||||||
|
script: [
|
||||||
|
{
|
||||||
|
type: 'application/ld+json',
|
||||||
|
innerHTML: JSON.stringify(breadcrumbSchema.value),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<LayoutsHeader />
|
<LayoutsHeader />
|
||||||
|
|||||||
@@ -1,4 +1,68 @@
|
|||||||
<script setup lang="ts"></script>
|
<script setup lang="ts">
|
||||||
|
const route = useRoute()
|
||||||
|
const { locale } = useI18n()
|
||||||
|
const gameDataStore = useGameDataStore()
|
||||||
|
const { gameName } = storeToRefs(gameDataStore)
|
||||||
|
const runtimeConfig = useRuntimeConfig()
|
||||||
|
|
||||||
|
// BreadcrumbList Schema 생성
|
||||||
|
const breadcrumbSchema = computed(() => {
|
||||||
|
if (!gameName.value || !runtimeConfig.public.baseDomain) return null
|
||||||
|
|
||||||
|
const baseUrl = `https://${runtimeConfig.public.baseDomain}`
|
||||||
|
const pathSegments = route.path.split('/').filter(Boolean)
|
||||||
|
const currentLocale = pathSegments[0] || locale.value
|
||||||
|
|
||||||
|
const itemListElement: Array<{
|
||||||
|
'@type': string
|
||||||
|
position: number
|
||||||
|
name: string
|
||||||
|
item: string
|
||||||
|
}> = [
|
||||||
|
{
|
||||||
|
'@type': 'ListItem',
|
||||||
|
position: 1,
|
||||||
|
name: gameName.value,
|
||||||
|
item: `${baseUrl}/${currentLocale}`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
// 로케일 이후의 경로 세그먼트들을 breadcrumb에 추가
|
||||||
|
pathSegments.slice(1).forEach((segment, index) => {
|
||||||
|
const path = `/${pathSegments.slice(0, index + 2).join('/')}`
|
||||||
|
const displayName = segment
|
||||||
|
.replace(/-/g, ' ')
|
||||||
|
.replace(/\b\w/g, l => l.toUpperCase())
|
||||||
|
|
||||||
|
itemListElement.push({
|
||||||
|
'@type': 'ListItem',
|
||||||
|
position: index + 2,
|
||||||
|
name: displayName,
|
||||||
|
item: `${baseUrl}${path}`,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'BreadcrumbList',
|
||||||
|
itemListElement,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Schema를 head에 추가
|
||||||
|
watchEffect(() => {
|
||||||
|
if (breadcrumbSchema.value) {
|
||||||
|
useHead({
|
||||||
|
script: [
|
||||||
|
{
|
||||||
|
type: 'application/ld+json',
|
||||||
|
innerHTML: JSON.stringify(breadcrumbSchema.value),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<LayoutsHeader />
|
<LayoutsHeader />
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export default defineNitroPlugin(nitroApp => {
|
|||||||
|
|
||||||
// 헬스체크 경로 체크 함수 추가
|
// 헬스체크 경로 체크 함수 추가
|
||||||
const isHealthCheck = (path: string): boolean => {
|
const isHealthCheck = (path: string): boolean => {
|
||||||
return path === '/health'
|
return path === '/health' || path === '/api/healthz'
|
||||||
}
|
}
|
||||||
|
|
||||||
nitroApp.hooks.hook('request', event => {
|
nitroApp.hooks.hook('request', event => {
|
||||||
@@ -33,22 +33,23 @@ export default defineNitroPlugin(nitroApp => {
|
|||||||
const method = event.method || ''
|
const method = event.method || ''
|
||||||
const headers = JSON.stringify(event.node.req.headers, null, 2)
|
const headers = JSON.stringify(event.node.req.headers, null, 2)
|
||||||
const requestId = generateRequestId()
|
const requestId = generateRequestId()
|
||||||
|
const domain = event.node.req.headers.host || 'unknown'
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'development') {
|
// if (process.env.NODE_ENV !== 'development') {
|
||||||
console.log(
|
console.log(
|
||||||
`Request Info {"requestId":"${requestId}", "type":"request","method":"${method}","url":"${event.path}","userIp":"${getIpAddress(event)}","userAgent":"${userAgent}", "headers" : "${headers}" }`
|
`Request Info {"requestId":"${requestId}", "type":"request","method":"${method}","domain":"${domain}","url":"${event.path}","userIp":"${getIpAddress(event)}","userAgent":"${userAgent}", "headers" : "${headers}" }`
|
||||||
)
|
)
|
||||||
|
|
||||||
// 요청 완료 후 응답 상태 코드 로깅
|
// 요청 완료 후 응답 상태 코드 로깅
|
||||||
event.node.res.on('finish', () => {
|
event.node.res.on('finish', () => {
|
||||||
console.log(
|
console.log(
|
||||||
`Response Info {"requestId":"${requestId}","type":"response","method":"${method}","url":"${event.path}","statusCode":${event.node.res.statusCode},"responseTime":"${Date.now() - startTime}ms","userIp":"${getIpAddress(event)}","userAgent":"${userAgent}","statusMessage":"${event.node.res.statusMessage}","responseHeader": ${JSON.stringify(event.node.res.getHeaders(), null, 2)}}`
|
`Response Info {"requestId":"${requestId}","type":"response","method":"${method}","domain":"${domain}","url":"${event.path}","statusCode":${event.node.res.statusCode},"responseTime":"${Date.now() - startTime}ms","userIp":"${getIpAddress(event)}","userAgent":"${userAgent}","statusMessage":"${event.node.res.statusMessage}","responseHeader": ${JSON.stringify(event.node.res.getHeaders(), null, 2)}}`
|
||||||
)
|
)
|
||||||
console.log(
|
console.log(
|
||||||
'==========================================================================================================================================================================================================================================================='
|
'==========================================================================================================================================================================================================================================================='
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
// }
|
||||||
})
|
})
|
||||||
|
|
||||||
nitroApp.hooks.hook('error', error => {
|
nitroApp.hooks.hook('error', error => {
|
||||||
|
|||||||
@@ -9,34 +9,40 @@ type RobotsConfig = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default defineEventHandler(async event => {
|
export default defineEventHandler(async event => {
|
||||||
const host =
|
// Nuxt runtimeConfig 사용
|
||||||
(getHeader(event, 'host') || getRequestHost(event)).toString() || ''
|
const runtimeConfig = useRuntimeConfig()
|
||||||
const baseDomain = process.env.BASE_DOMAIN || '.onstove.com'
|
const baseDomain = runtimeConfig.public.baseDomain
|
||||||
|
const staticUrl = runtimeConfig.public.staticUrl
|
||||||
|
const runType = runtimeConfig.public.runType
|
||||||
|
|
||||||
|
// 프록시 뒤에 있는 경우 X-Forwarded-Host 우선 사용
|
||||||
|
const forwardedHost = getHeader(event, 'x-forwarded-host')
|
||||||
|
const host = (
|
||||||
|
forwardedHost ||
|
||||||
|
getHeader(event, 'host') ||
|
||||||
|
getRequestHost(event)
|
||||||
|
).toString() || ''
|
||||||
const isGameAliasExtractable = host.includes(baseDomain)
|
const isGameAliasExtractable = host.includes(baseDomain)
|
||||||
|
|
||||||
let gameAlias = ''
|
let gameAlias = ''
|
||||||
if (isGameAliasExtractable) {
|
if (isGameAliasExtractable) {
|
||||||
gameAlias = host.split('.')[0]
|
gameAlias = host.split('.')[0].replace(/-dev$/, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (gameAlias && gameAlias !== "www") {
|
|
||||||
// event.context.gameAlias = gameAlias;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// robots 설정을 직접 가져오기 (미들웨어 context 사용)
|
|
||||||
|
|
||||||
let config: RobotsConfig
|
let config: RobotsConfig
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// robots 설정 추출
|
// gameAlias가 있을 때만 sitemap 포함
|
||||||
|
const sitemapUrl = gameAlias
|
||||||
|
? [`${staticUrl}/${runType}/template/${gameAlias}/sitemap.xml`]
|
||||||
|
: undefined
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
userAgent: '*',
|
userAgent: '*',
|
||||||
allow: ['/'],
|
allow: ['/'],
|
||||||
disallow: ['/error', '/inspection/', '/inspection/*', '/html/*'],
|
disallow: ['/error', '/inspection/*', '/html/*'],
|
||||||
sitemap: [
|
sitemap: sitemapUrl,
|
||||||
`https://static-pubcomm.gate8.com/local/template/${gameAlias}/sitemap.xml`,
|
host: gameAlias ? `${gameAlias}.onstove.com` : undefined,
|
||||||
],
|
|
||||||
host: `${gameAlias}.onstove.com`,
|
|
||||||
cache: { sMaxAge: 300, staleWhileRevalidate: 600 },
|
cache: { sMaxAge: 300, staleWhileRevalidate: 600 },
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -46,7 +52,7 @@ export default defineEventHandler(async event => {
|
|||||||
config = {
|
config = {
|
||||||
userAgent: '*',
|
userAgent: '*',
|
||||||
allow: ['/'],
|
allow: ['/'],
|
||||||
disallow: ['/error', '/inspection/', '/inspection/*', '/html/*'],
|
disallow: ['/error', '/inspection/*', '/html/*'],
|
||||||
cache: { sMaxAge: 300, staleWhileRevalidate: 600 },
|
cache: { sMaxAge: 300, staleWhileRevalidate: 600 },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,8 +86,14 @@ export default defineEventHandler(async event => {
|
|||||||
: config.sitemap
|
: config.sitemap
|
||||||
? [config.sitemap]
|
? [config.sitemap]
|
||||||
: []
|
: []
|
||||||
for (const sm of sitemaps) lines.push(`Sitemap: ${sm}`)
|
|
||||||
if (config.host) lines.push(`Host: ${config.host}`)
|
for (const sm of sitemaps) {
|
||||||
|
lines.push(`Sitemap: ${sm}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.host) {
|
||||||
|
lines.push(`Host: ${config.host}`)
|
||||||
|
}
|
||||||
|
|
||||||
// 마지막 개행
|
// 마지막 개행
|
||||||
return lines.join('\n').trim() + '\n'
|
return lines.join('\n').trim() + '\n'
|
||||||
|
|||||||
Reference in New Issue
Block a user