103 lines
2.5 KiB
Vue
103 lines
2.5 KiB
Vue
<script setup>
|
|
definePageMeta({
|
|
layout: 'post'
|
|
})
|
|
|
|
const route = useRoute()
|
|
const slug = computed(() => String(route.params.slug || ''))
|
|
|
|
const { data: post } = await useFetch(() => `/api/posts/${slug.value}`)
|
|
|
|
if (!post.value) {
|
|
throw createError({
|
|
statusCode: 404,
|
|
statusMessage: '게시물을 찾을 수 없습니다.'
|
|
})
|
|
}
|
|
|
|
const postTag = computed(() => post.value.tags?.[0]?.toUpperCase() || 'POST')
|
|
const config = useRuntimeConfig()
|
|
const siteUrl = computed(() => String(config.public.siteUrl || '').replace(/\/$/g, ''))
|
|
const pageUrl = computed(() => `${siteUrl.value}/post/${post.value.slug}`)
|
|
const seoTitle = computed(() => post.value.seoTitle || post.value.title)
|
|
const seoDescription = computed(() => post.value.seoDescription || post.value.excerpt || 'sori.studio 개인 블로그')
|
|
const canonicalUrl = computed(() => post.value.canonicalUrl || pageUrl.value)
|
|
const ogImage = computed(() => post.value.ogImage || post.value.featuredImage || '')
|
|
|
|
/**
|
|
* 절대 URL 생성
|
|
* @param {string} value - 원본 URL
|
|
* @returns {string} 절대 URL
|
|
*/
|
|
const toAbsoluteUrl = (value) => {
|
|
if (!value) {
|
|
return ''
|
|
}
|
|
|
|
if (/^https?:\/\//i.test(value)) {
|
|
return value
|
|
}
|
|
|
|
return `${siteUrl.value}${value.startsWith('/') ? value : `/${value}`}`
|
|
}
|
|
|
|
useHead(() => ({
|
|
title: seoTitle.value,
|
|
link: [
|
|
{
|
|
rel: 'canonical',
|
|
href: canonicalUrl.value
|
|
}
|
|
],
|
|
meta: [
|
|
{
|
|
name: 'description',
|
|
content: seoDescription.value
|
|
},
|
|
{
|
|
name: 'robots',
|
|
content: post.value.noindex ? 'noindex, nofollow' : 'index, follow'
|
|
},
|
|
{
|
|
property: 'og:title',
|
|
content: seoTitle.value
|
|
},
|
|
{
|
|
property: 'og:description',
|
|
content: seoDescription.value
|
|
},
|
|
{
|
|
property: 'og:url',
|
|
content: pageUrl.value
|
|
},
|
|
...(ogImage.value
|
|
? [
|
|
{
|
|
property: 'og:image',
|
|
content: toAbsoluteUrl(ogImage.value)
|
|
},
|
|
{
|
|
name: 'twitter:card',
|
|
content: 'summary_large_image'
|
|
}
|
|
]
|
|
: [])
|
|
]
|
|
}))
|
|
</script>
|
|
|
|
<template>
|
|
<ContentRenderer>
|
|
<ProseHeaderCard>
|
|
<p class="post-detail__eyebrow text-sm uppercase text-white/70">
|
|
{{ postTag }}
|
|
</p>
|
|
<h1 class="post-detail__title mt-3 text-4xl font-semibold leading-tight">
|
|
{{ post.title }}
|
|
</h1>
|
|
</ProseHeaderCard>
|
|
|
|
<ContentMarkdownRenderer class="post-detail__content" :content="post.content" />
|
|
</ContentRenderer>
|
|
</template>
|