refactor: 에러 처리 완료, 페이지 접근 api 미들웨어로 수정

This commit is contained in:
“hyeonggkim”
2025-11-11 19:08:58 +09:00
parent 65c79eb689
commit 5c81f5d4d6
12 changed files with 318 additions and 276 deletions

View File

@@ -1,120 +0,0 @@
# Dynamic I18n Routes - gameData.lang_codes 기반 언어 제외 설정
이 문서는 `gameDataFromServer.lang_codes`를 기반으로 특정 언어들을 제외하는 Ignoring Localized Routes 기능의 사용법을 설명합니다.
## 구현된 기능
### 1. 동적 언어 제외 설정
- `gameDataFromServer.lang_codes`에 포함된 언어만 허용
- 포함되지 않은 언어는 자동으로 제외 처리
- 런타임에 동적으로 언어 설정 변경
### 2. 구현된 파일들
#### `i18n.config.ts`
- `getI18n()` 함수에 `allowedLangCodes` 매개변수 추가
- `generatePageExclusions()` 함수로 언어 제외 설정 생성
- `customRoutes: 'config'` 설정으로 페이지별 언어 제외 지원
#### `layers/composables/useDynamicI18nRoutes.ts`
- `getAllowedLangCodes()`: 허용된 언어 목록 반환
- `isLangAllowed()`: 특정 언어 허용 여부 확인
- `getI18nRouteConfig()`: `defineI18nRoute`용 설정 생성
- `getExcludedLangConfig()`: 특정 언어 제외 설정 생성
#### `layers/plugins/dynamic-i18n-routes.client.ts`
- 클라이언트 사이드에서 gameData 변경 감지
- 언어 설정 동적 업데이트
#### `layers/plugins/dynamic-i18n-routes.server.ts`
- 서버 사이드에서 gameData 기반 언어 설정 적용
## 사용법
### 1. 페이지에서 동적 언어 제외 설정
```vue
<script setup lang="ts">
// 동적 i18n 라우트 설정
const { getI18nRouteConfig } = useDynamicI18nRoutes();
// gameData.lang_codes를 기반으로 동적 언어 제외 설정
const i18nRouteConfig = getI18nRouteConfig();
if (i18nRouteConfig) {
defineI18nRoute(i18nRouteConfig);
}
</script>
```
### 2. 특정 언어만 허용하는 경우
```vue
<script setup lang="ts">
const { getI18nRouteConfig } = useDynamicI18nRoutes();
// gameData.lang_codes에 포함된 언어만 허용
const i18nRouteConfig = getI18nRouteConfig();
if (i18nRouteConfig) {
defineI18nRoute(i18nRouteConfig);
}
</script>
```
### 3. 특정 언어를 제외하는 경우
```vue
<script setup lang="ts">
const { getExcludedLangConfig } = useDynamicI18nRoutes();
// 특정 언어들을 제외 (예: 'fr', 'de' 제외)
const excludedConfig = getExcludedLangConfig(['fr', 'de']);
if (excludedConfig) {
defineI18nRoute(excludedConfig);
}
</script>
```
### 4. 언어 허용 여부 확인
```vue
<script setup lang="ts">
const { isLangAllowed } = useDynamicI18nRoutes();
// 특정 언어가 허용되는지 확인
const isFrenchAllowed = isLangAllowed('fr');
const isKoreanAllowed = isLangAllowed('ko');
</script>
```
## 동작 원리
1. **서버 사이드**: `gameDataFromServer.lang_codes`를 기반으로 초기 언어 설정 적용
2. **클라이언트 사이드**: gameData 변경 시 언어 설정 동적 업데이트
3. **페이지 레벨**: `defineI18nRoute`를 통해 페이지별 언어 제외 설정
4. **자동 리다이렉트**: 허용되지 않은 언어로 접근 시 기본 언어로 리다이렉트
## 예시 시나리오
### 시나리오 1: 게임별 언어 제한
```typescript
// gameData.lang_codes = ['ko', 'en', 'ja']
// 결과: 한국어, 영어, 일본어만 허용, 나머지 언어는 자동 제외
```
### 시나리오 2: 특정 언어 제외
```typescript
// gameData.lang_codes = ['ko', 'en', 'ja', 'zh-tw', 'fr', 'de', 'es', 'pt', 'th', 'zh-cn']
// getExcludedLangConfig(['fr', 'de']) 호출
// 결과: 프랑스어, 독일어 제외, 나머지 언어 허용
```
## 주의사항
1. `defineI18nRoute`는 컴파일 타임에 실행되므로, 동적 설정이 필요한 경우 `watchEffect``watch`를 사용
2. SSR과 클라이언트 사이드 모두에서 일관된 동작을 위해 플러그인 사용 권장
3. 언어 변경 시 사용자 경험을 고려한 적절한 리다이렉트 처리 필요
## 참고 문서
- [Nuxt i18n - Ignoring Localized Routes](https://i18n.nuxtjs.org/docs/guide/ignoring-localized-routes)
- [Nuxt i18n - defineI18nRoute](https://i18n.nuxtjs.org/docs/compiler-macros/define-i18n-route)

105
app/error.vue Normal file
View File

@@ -0,0 +1,105 @@
<template>
<div class="min-h-[100vh] flex flex-col bg-[#F0F0F0] pb-12">
<LayoutsStoveHeader />
<Transition name="fade">
<div v-if="!isLoading" class="flex-1 flex items-center justify-center p-25">
<div class="flex flex-col items-center gap-6 w-full">
<!-- Stove Logo -->
<div class="flex items-center justify-center h-7">
<img
src="/images/common/logo-stove.svg"
alt="Stove"
class="h-full w-auto"
/>
</div>
<!-- Error Icon and Text -->
<div class="flex flex-col items-center gap-4 w-full">
<!-- Error Icon -->
<div class="flex items-center justify-center">
<img
src="/images/common/img_error.png"
alt="Error"
class="w-40 h-40 md:w-60 md:h-60"
/>
</div>
<!-- Error Text -->
<div class="flex flex-col items-center gap-2 w-full">
<h1 class="font-medium text-xl md:text-2xl leading-[1.5] tracking-[-0.03em] text-center text-[#1F1F1F] m-0">
<span v-dompurify-html="errorTitle"></span>
</h1>
<p v-dompurify-html="errorMsg" class="font-normal text-sm md:text-base leading-[1.7] md:leading-[1.625] tracking-[-0.03em] text-center text-[#666666] m-0"></p>
</div>
</div>
<!-- Home Button -->
<AtomsButton
type="action"
button-size="size-small md:size-large"
background-color="#FC4420"
text-color="#FFFFFF"
@click="handleError"
>
<span v-dompurify-html="tm('Error_Official_Page')"></span>
</AtomsButton>
</div>
</div>
</Transition>
</div>
</template>
<script setup lang="ts">
interface ErrorProps {
error?: {
statusCode?: number
statusMessage?: string
message?: string
}
}
const { tm } = useI18n()
const props = withDefaults(defineProps<ErrorProps>(), {
error: () => ({})
})
const nuxtError = useError()
const currentError = computed(() => props.error || nuxtError.value)
const isLoading = ref(true)
const errorTitle = ref('')
const errorMsg = ref('')
//error clear 함수 생성
const localePath = useLocalePath()
// const handleError = () => clearError({ redirect: '/' })
const handleError = () => {
window.location.href = localePath('/')
// clearError({ redirect: `${localePath('/')}` })
}
// 500 에러 발생 시 /error 페이지로 리다이렉트
onMounted(() => {
const statusCode = currentError.value?.statusCode
console.log("🚀 ~ 2222 currentError==:", currentError.value?.message)
console.log("🚀 ~ 222222 statusCode:", statusCode)
if (statusCode === 500) {
errorTitle.value = tm('Error_500_Inconvenience')
errorMsg.value = tm('Error_500_Sorry')
} else {
errorTitle.value = tm('Error_404_Not_Found')
errorMsg.value = tm('Error_404_Not_Found2')
}
nextTick(() => {
isLoading.value = false
})
})
</script>

View File

@@ -1,59 +0,0 @@
<template>
<div class="min-h-[100vh] flex flex-col bg-[#F0F0F0] pb-12">
<LayoutsStoveHeader />
<Transition name="fade">
<div v-if="!isLoading" class="flex-1 flex items-center justify-center p-25">
<div class="flex flex-col items-center gap-6 w-full">
<!-- Stove Logo -->
<div class="flex items-center justify-center h-7">
<img
src="/images/common/logo-stove.svg"
alt="Stove"
class="h-full w-auto"
/>
</div>
<!-- Error Icon and Text -->
<div class="flex flex-col items-center gap-4 w-full">
<!-- Error Icon -->
<div class="flex items-center justify-center">
<img
src="/images/common/img_error.png"
alt="Error"
class="w-40 h-40 md:w-60 md:h-60"
/>
</div>
<!-- Error Text -->
<div class="flex flex-col items-center gap-2 w-full">
<h1 class="font-medium text-xl md:text-2xl leading-[1.5] tracking-[-0.03em] text-center text-[#1F1F1F] m-0">
<span v-dompurify-html="tm('Error_404_Not_Found')"></span>
</h1>
<p v-dompurify-html="tm('Error_404_Not_Found2')" class="font-normal text-sm md:text-base leading-[1.7] md:leading-[1.625] tracking-[-0.03em] text-center text-[#666666] m-0"></p>
</div>
</div>
<!-- Home Button -->
<AtomsLocaleLink to="/brand" class="flex items-center justify-center font-medium text-sm leading-[1.4] tracking-[-0.03em] text-white bg-[#FC4420] border border-black/10 rounded-lg px-10 h-12 cursor-pointer transition-all duration-300 backdrop-blur-[30px] shadow-[0px_0.9131946563720703px_1.4666459560394287px_0px_rgba(0,0,0,0.06),0px_2.194533586502075px_3.5245540142059326px_0px_rgba(0,0,0,0.09)] hover:bg-[#E03D1C] hover:shadow-[0px_0.9131946563720703px_1.4666459560394287px_0px_rgba(0,0,0,0.06),0px_2.194533586502075px_3.5245540142059326px_0px_rgba(0,0,0,0.09),0px_4.1321120262146px_6.636422634124756px_0px_rgba(0,0,0,0.12)] active:translate-y-px">
<span v-dompurify-html="tm('Error_Official_Page')"></span>
</AtomsLocaleLink>
</div>
</div>
</Transition>
</div>
</template>
<script setup lang="ts">
const { tm } = useI18n()
const isLoading = ref(true)
onMounted(() => {
nextTick(() => {
isLoading.value = false
})
})
</script>

114
error.vue
View File

@@ -1,14 +1,16 @@
<template>
<div class="min-h-screen flex flex-col bg-[#191919]">
<div class="min-h-[100vh] flex flex-col bg-[#F0F0F0] pb-12">
<LayoutsStoveHeader />
<div class="flex-1 flex items-center justify-center bg-[#F0F0F0] pt-[223px] pb-[223px] px-5 max-[374px]:px-5 md:px-[530px]">
<div class="flex flex-col items-center gap-6 w-full max-w-[513px] max-[374px]:max-w-[335px] md:max-w-[860px] py-10 max-[374px]:gap-6 md:gap-6 md:py-20">
<Transition name="fade">
<div v-if="!isLoading" class="flex-1 flex items-center justify-center p-25">
<div class="flex flex-col items-center gap-6 w-full">
<!-- Stove Logo -->
<div class="flex items-center justify-center w-[123.2px] h-7">
<div class="flex items-center justify-center h-7">
<img
src="/images/common/logo-stove.svg"
alt="Stove"
class="w-full h-auto"
class="h-full w-auto"
/>
</div>
@@ -24,72 +26,70 @@
</div>
<!-- Error Text -->
<div class="flex flex-col items-center gap-1 w-full">
<h1 class="font-['Spoqa_Han_Sans_Neo',sans-serif] font-medium text-xl md:text-2xl leading-[1.5] md:leading-[1.4166666666666667] tracking-[-0.03em] text-center text-[#1F1F1F] m-0">
{{ errorTitle }}
<div class="flex flex-col items-center gap-2 w-full">
<h1 class="font-medium text-xl md:text-2xl leading-[1.5] tracking-[-0.03em] text-center text-[#1F1F1F] m-0">
<span v-dompurify-html="errorTitle"></span>
</h1>
<p class="font-['Spoqa_Han_Sans_Neo',sans-serif] font-normal text-sm md:text-base leading-[1.7142857142857142] md:leading-[1.625] tracking-[-0.03em] text-center text-[#666666] m-0">
{{ errorDescription }}
</p>
<p v-dompurify-html="errorMsg" class="font-normal text-sm md:text-base leading-[1.7] md:leading-[1.625] tracking-[-0.03em] text-center text-[#666666] m-0"></p>
</div>
</div>
<!-- Home Button -->
<button
class="flex items-center justify-center font-['Spoqa_Han_Sans_Neo',sans-serif] font-medium text-sm leading-[1.4285714285714286em] tracking-[-0.03em] text-white bg-[#FC4420] border border-black/10 rounded-lg px-10 h-12 cursor-pointer transition-all duration-300 backdrop-blur-[30px] shadow-[0px_0.9131946563720703px_1.4666459560394287px_0px_rgba(0,0,0,0.06),0px_2.194533586502075px_3.5245540142059326px_0px_rgba(0,0,0,0.09)] hover:bg-[#E03D1C] hover:shadow-[0px_0.9131946563720703px_1.4666459560394287px_0px_rgba(0,0,0,0.06),0px_2.194533586502075px_3.5245540142059326px_0px_rgba(0,0,0,0.09),0px_4.1321120262146px_6.636422634124756px_0px_rgba(0,0,0,0.12)] active:translate-y-px"
@click="handleGoHome"
>
{{ homeButtonText }}
</button>
<AtomsButton class="flex items-center justify-center font-medium text-sm leading-[1.4] tracking-[-0.03em] text-white bg-[#FC4420] border border-black/10 rounded-lg px-10 h-12 cursor-pointer transition-all duration-300 backdrop-blur-[30px] shadow-[0px_0.9131946563720703px_1.4666459560394287px_0px_rgba(0,0,0,0.06),0px_2.194533586502075px_3.5245540142059326px_0px_rgba(0,0,0,0.09)] hover:bg-[#E03D1C] hover:shadow-[0px_0.9131946563720703px_1.4666459560394287px_0px_rgba(0,0,0,0.06),0px_2.194533586502075px_3.5245540142059326px_0px_rgba(0,0,0,0.09),0px_4.1321120262146px_6.636422634124756px_0px_rgba(0,0,0,0.12)] active:translate-y-px" @click="handleError">
<span v-dompurify-html="tm('Error_Official_Page')"></span>
</AtomsButton>
</div>
</div>
</div>
</Transition>
</div>
</template>
<script setup lang="ts">
import type { NuxtError } from '#app'
import { useGameDataStore } from '#layers/stores/useGameDataStore'
import { useLocalePath } from '#i18n'
const props = defineProps({
error: Object as () => NuxtError,
})
const gameDataStore = useGameDataStore()
const { gameData } = storeToRefs(gameDataStore)
const localePath = useLocalePath()
const gameName = computed(() => gameData.value?.game_name || '게임')
const homeButtonText = computed(() => `${gameName.value} 홈페이지 가기`)
const handleGoHome = () => {
const homePath = localePath('/')
navigateTo(homePath)
interface ErrorProps {
error?: {
statusCode?: number
statusMessage?: string
message?: string
}
}
// 에러 상태 코드에 따른 메시지 설정
const errorTitle = computed(() => {
if (props.error?.statusCode === 404) {
return '페이지를 찾을 수 없어요.'
const { tm } = useI18n()
const props = withDefaults(defineProps<ErrorProps>(), {
error: () => ({})
})
const nuxtError = useError()
const currentError = computed(() => props.error || nuxtError.value)
const isLoading = ref(true)
const errorTitle = ref('')
const errorMsg = ref('')
//error clear 함수 생성
const handleError = () => clearError({ redirect: '/' })
// 500 에러 발생 시 /error 페이지로 리다이렉트
onMounted(() => {
const statusCode = currentError.value?.statusCode
console.log("🚀 ~ 1111currentError==:", currentError.value?.message)
console.log("🚀 ~ 1111statusCode:", statusCode)
if (statusCode === 500) {
errorTitle.value = tm('Error_500_Inconvenience')
errorMsg.value = tm('Error_500_Sorry')
} else {
errorTitle.value = tm('Error_404_Not_Found')
errorMsg.value = tm('Error_404_Not_Found2')
}
return '페이지를 찾을 수 없어요.'
})
const errorDescription = computed(() => {
if (props.error?.statusCode === 404) {
return '주소가 바뀌었거나 잘못 입력된 것 같아요. 주소를 다시 확인해 주세요.'
}
return '주소가 바뀌었거나 잘못 입력된 것 같아요. 주소를 다시 확인해 주세요.'
nextTick(() => {
isLoading.value = false
})
})
definePageMeta({
layout: 'only-stove'
})
</script>
<style scoped>
/* :deep(.game-wrapper) {
display: none !important;
} */
</style>

View File

@@ -66,10 +66,10 @@ const getI18n = (allowedLangCodes?: string[]): NuxtI18nOptions => {
debug: false,
// 동적으로 언어 제외 설정을 위한 pages 설정
customRoutes: 'config',
pages:
allowedLangCodes && allowedLangCodes.length > 0
? generatePageExclusions(allowedLangCodes)
: {},
// pages:
// allowedLangCodes && allowedLangCodes.length > 0
// ? generatePageExclusions(allowedLangCodes)
// : {},
// 추가적인 설정이 필요하다면 여기에 포함시킬 수 있습니다.
}
}

View File

@@ -7,24 +7,29 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
}
// 현재 경로에서 언어 코드 추출
// 예: /ko/about/story -> ko
// 예: /en/test/page -> en
const gameDataStore = useGameDataStore()
const gameData = gameDataStore.gameData as GameDataValue
const langCodes = gameData?.lang_codes
const currentLangCode = csrGetFinalLocale(to.path, langCodes)
const { getPathAfterLanguage } = usePathResolver()
const pageUrl = getPathAfterLanguage(to.path)
// const languagePattern = /^\/([a-z]{2})(?:\/|$)/
// const match = to.path.match(languagePattern)
// const currentLangCode = match ? match[1] : null
console.log("🚀 777777 ~ currentLangCode:", currentLangCode)
//현재 url에서 게임 도메인만 추출
const currentDomain = window.location.hostname
const runtimeConfig = useRuntimeConfig()
// 쿼리스트링에서 f 파라미터 값 추출 (CSR용)
const fValue = (to.query.f as string) || ''
// 미리보기 API 호출 처리
let finalGameDomain = currentDomain
if (fValue === 'preview') {
finalGameDomain = 'samplegame.onstove.com'
}
const req: GameDataRequest = {
gameDomain: `${currentDomain}`,
gameDomain: `${finalGameDomain}`,
langCode: `${currentLangCode}`,
game_alias: '',
lang_code: `${currentLangCode}`,
@@ -34,16 +39,19 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
const { getGameDataExternal } = useGetGameDataExternal()
await getGameDataExternal(req)
// 허용된 언어 코드 목록≈≈
const allowedLangCodes = langCodes || []
// error 페이지는 API 호출하지 않음
if (pageUrl === '/error' || to.path.includes('/error')) {
return
}
// 현재 언어가 허용된 언어 목록에 없으면 에러 페이지로 이동
if (currentLangCode && !allowedLangCodes.includes(currentLangCode)) {
throw createError({
statusCode: 404,
statusMessage: 'Language not supported',
})
return navigateTo(`/${currentLangCode}/error`, { external: false })
// throw createError({
// statusCode: 404,
// statusMessage: 'Language not supported11',
// })
}
})

View File

@@ -1,6 +1,19 @@
export default defineNuxtRouteMiddleware(async to => {
try {
//error 발생시에는 미들웨어 실행하지 않음
//error 객체 조회
if (import.meta.client) {
const error = useError()
if(error.value?.statusCode){
return showError(createError({
statusCode: error.value?.statusCode,
statusMessage: error.value?.message,
fatal: true,
// data: { path: to.path }
}))
}
const gameDataStore = useGameDataStore()
const { gameData } = storeToRefs(gameDataStore)
console.log('🚀 ~ 00000 gameData:', gameData.value)
@@ -40,6 +53,7 @@ export default defineNuxtRouteMiddleware(async to => {
!to.path.includes('inspection') &&
!to.path.includes('api')
) {
console.log('e111111111 eee')
// 점검 중인 경우
return navigateTo(`/${finalLocale}/inspection`, { external: true })
} else if (
@@ -47,8 +61,11 @@ export default defineNuxtRouteMiddleware(async to => {
to.path?.indexOf('inspection') !== -1
) {
// 점검이 종료된 후 점검 페이지 접근시 메인으로 리다이렉트
console.log('ddddd 222222')
return navigateTo(`/${finalLocale}`, { external: true })
}
console.log('33333333 eee')
}
} catch (e) {
console.error('[Exception] /middleware/inspection: ', e)

View File

@@ -6,7 +6,7 @@ import type { PageDataResponse } from '#layers/types/api/pageData'
import type { GameDataValue } from '#layers/types/api/gameData'
export default defineNuxtRouteMiddleware(async (to, _from) => {
// [TODO] 하이드레이션 에러 처리
// client에서만 동작되도록 처리
if (!import.meta.client) return
const runtimeConfig = useRuntimeConfig()
@@ -28,10 +28,7 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
)
try {
// 서버 사이드에서는 스킵
if (import.meta.server) {
return
}
if (to.path.includes('inspection')) {
return
}
@@ -45,11 +42,26 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
// pageUrl이 빈값이거나 null이면 /brand로 리다이렉트
if (!pageUrl || pageUrl === '' || pageUrl === '/' || pageUrl === `/${langCode}/`) {
return navigateTo(`/${langCode}/brand`, { replace: true })
return navigateTo(`/${langCode}/brand`, { external: false })
}
const accessToken = csrGetAccessToken()
const headers = {
Authorization: `Bearer ${accessToken}`,
}
// 쿼리스트링에서 f 파라미터 값 추출 (CSR용)
const fValue = (to.query.f as string) || ''
// 미리보기 API 호출 처리
let finalGameDomain = gameDomain
if (fValue === 'preview') {
finalGameDomain = 'samplegame.onstove.com'
}
const queryParams: Record<string, string> = {
game_domain: gameDomain,
game_domain: finalGameDomain,
lang_code: langCode,
page_url: pageUrl,
_t: Date.now().toString(), // 캐시 무효화를 위한 타임스탬프
@@ -57,13 +69,37 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
// console.log('🚀 ~ queryParams:', queryParams)
const response = (await commonFetch('GET', apiUrl, {
headers,
query: queryParams,
loading: true,
})) as PageDataResponse | null
console.log('🚀 ~ response?.code:', response?.code)
// 페이지 접근 권한 설정(로그인 유무)
if(response?.value?.is_login_required === 1 && !accessToken) {
// 로그인 레이어 팝업 띄워주기
const nuxtApp = useNuxtApp()
const modalStore = useModalStore()
const $i18n = nuxtApp.$i18n as any
const {tm} = $i18n
modalStore.handleOpenConfirm({
contentText: tm('Alert_StoveLogin'),
confirmButtonText: tm('Text_StoveLogin'),
confirmButtonEvent: () => {
csrGoStoveLogin()
},
})
}
if(response?.code === 91003) {
return navigateTo(`/${langCode}/error`, { external: false })
// return navigateTo(`/${langCode}/error`, { external: false })
showError(createError({
statusCode: 404,
statusMessage: '페이지를 찾을 수 없어요.',
fatal: false, // 즉시 에러 페이지로
data: { reason: 'post-not-found' }
}))
}
if (response?.code === 0 && 'value' in response) {
@@ -74,5 +110,12 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
} catch (error) {
console.error(error)
store.clearPageData()
showError(createError({
statusCode: error.statusCode,
statusMessage: error.message,
fatal: false, // 즉시 에러 페이지로
data: { reason: 'post-not-found' }
}))
}
})

View File

@@ -1,12 +0,0 @@
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
console.log("🚀 000000 ~ error:", error)
// handle error, e.g. report to a service
}
// Also possible
nuxtApp.hook('vue:error', (error, instance, info) => {
console.log("🚀1111 ~ error:", error)
// handle error, e.g. report to a service
})
})

View File

@@ -96,6 +96,7 @@ function fnLocaleMiddleware(event: any, finalLocale: string) {
// 쿼리스트링 포함 시 순수 경로만 추출
arrPath = path.split('?')[0].split('/')
queryString = path.split('?')[1]
} else {
arrPath = path.split('/')
queryString = ''
@@ -239,6 +240,33 @@ export default defineEventHandler(async event => {
// 2. 언어 코드 추출
finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers, initLangCodes, initDefaultLocale)
const path = event?.node.req.url || ''
let queryStringF = ''
let fValue = ''
let test500 = false
if (path.includes('?')) {
queryStringF = path.split('?')[1]
// 쿼리스트링에서 f 파라미터 값 추출
try {
const urlParams = new URLSearchParams(queryStringF)
fValue = urlParams.get('f') || ''
// 테스트용 500 에러 발생 (예: ?test500=true)
test500 = urlParams.get('test500') === 'true'
} catch (e) {
console.error('쿼리스트링 파싱 에러:', e)
}
}
// 테스트용 500 에러 발생
if (test500) {
throw new Error('테스트용 500 에러 발생')
}
// 미리보기 API 호출 처리
if (fValue === 'preview') {
cleanHost = 'samplegame.onstove.com'
}
const queryParams: Record<string, string> = {
game_domain: cleanHost || '',
lang_code: finalLocale,
@@ -385,5 +413,35 @@ export default defineEventHandler(async event => {
}
} catch (error) {
console.error('gameData load error:', error)
// 500 에러 발생 시 /error 페이지로 리다이렉트
if (!event.node.res.headersSent && !event.node.res.writableEnded) {
// 언어 코드 추출 시도
let finalLocale = 'ko' // 기본값
try {
finalLocale = ssrGetFinalLocale(
event?.node.req.url,
event.node.req.headers,
initLangCodes,
initDefaultLocale
)
} catch (e) {
console.error('Locale extraction error:', e)
}
// finalLocale이 undefined인 경우 기본값으로 'ko' 설정
console.log("🚀 ~ 여기도 타? error:", error)
throw createError({
statusCode: error.statusCode,
statusMessage: error.statusMessage,
})
// if (!finalLocale) {
// finalLocale = 'ko'
// }
// const errorPath = `/${finalLocale}/error?message=${error.message}`
// event.node.res.statusCode = 302
// event.node.res.setHeader('Location', errorPath)
// event.node.res.end()
}
}
})

View File

@@ -47,6 +47,7 @@ const checkLoginValidation = async () => {
const validateTokenResult = await handleTokenValidation(
accessToken.value || ''
)
console.log("🚀 ~ checkLoginValidation ~ validateTokenResult:", validateTokenResult)
isLogin.value = validateTokenResult
}

View File

@@ -28,6 +28,7 @@ export interface PageDataValue {
page_name: string
page_name_en: string
page_ver: string
is_login_required: number
meta_tag_type: number
fit_page_height: boolean
use_top_btn: boolean