게시물 이동 로딩 후 진입으로 복귀

This commit is contained in:
2026-07-06 13:30:21 +09:00
parent 34d2d8e2ea
commit 7685f82c77
12 changed files with 81 additions and 233 deletions

View File

@@ -9,17 +9,10 @@ const route = useRoute()
const slug = computed(() => String(route.params.slug || ''))
/**
* SSR 직접 접속은 본문을 먼저 받아 SEO를 유지하고, 클라이언트 내부 이동은 즉시 화면을 전환한다.
* 본문은 블로킹으로 받아 로딩 표시 후 완성된 화면으로 전환한다.
* 이전·다음 글은 상세 응답에 포함되어 별도 목록 요청 없이 표시된다.
*/
const {
data: fetchedPost,
pending: isPostPending,
error: postFetchError
} = await useFetch(() => `/api/posts/${slug.value}`, {
lazy: import.meta.client,
default: () => null
})
const { data: post } = await useFetch(() => `/api/posts/${slug.value}`)
const { data: tags } = useLazyFetch('/api/tags?tagType=all', {
default: () => []
})
@@ -29,15 +22,7 @@ const { data: siteSettings } = useLazyFetch('/api/site-settings', {
})
const postToc = useState('post-detail-toc', () => [])
const post = computed(() => (
fetchedPost.value?.slug === slug.value
? fetchedPost.value
: null
))
const isPostLoading = computed(() => isPostPending.value || (!post.value && !postFetchError.value))
if (import.meta.server && !post.value) {
if (!post.value) {
throw createError({
statusCode: 404,
statusMessage: '게시물을 찾을 수 없습니다.'
@@ -148,17 +133,6 @@ onMounted(() => {
fetchCurrentViewer()
})
watch([isPostPending, postFetchError, post], () => {
if (!import.meta.client || isPostPending.value || post.value) {
return
}
showError(createError({
statusCode: postFetchError.value?.statusCode || 404,
statusMessage: '게시물을 찾을 수 없습니다.'
}))
})
watchEffect(() => {
postToc.value = tableOfContents.value
})
@@ -279,7 +253,6 @@ useHead(() => ({
<template>
<div class="post-detail">
<template v-if="post">
<section class="mt-6 mb-8">
<div class="mx-auto flex max-w-[720px] flex-col gap-2.5 px-4 sm:px-5">
<h1 class="text-xl font-semibold leading-[1.125] sm:text-2xl">
@@ -547,49 +520,5 @@ useHead(() => ({
</div>
</div>
</Transition>
</template>
<section v-else class="post-detail__loading mt-6 mb-12" aria-live="polite" aria-busy="true">
<div class="mx-auto flex min-h-[70vh] max-w-[720px] flex-col gap-5 px-4 sm:px-5">
<div class="grid gap-3">
<div class="post-detail__loading-shimmer h-8 w-11/12 rounded bg-[var(--site-panel-strong)] sm:h-10" />
<div class="post-detail__loading-shimmer h-4 w-2/5 rounded bg-[var(--site-panel-strong)]" />
</div>
<div class="border-b border-[var(--site-line)] pb-4">
<div class="post-detail__loading-shimmer h-3 w-3/5 rounded bg-[var(--site-panel-strong)]" />
</div>
<div class="post-detail__loading-shimmer aspect-video w-full rounded-[10px] bg-[var(--site-panel-strong)]" />
<div class="mt-4 grid gap-3">
<div class="post-detail__loading-shimmer h-4 w-full rounded bg-[var(--site-panel-strong)]" />
<div class="post-detail__loading-shimmer h-4 w-[94%] rounded bg-[var(--site-panel-strong)]" />
<div class="post-detail__loading-shimmer h-4 w-[88%] rounded bg-[var(--site-panel-strong)]" />
<div class="post-detail__loading-shimmer h-4 w-[96%] rounded bg-[var(--site-panel-strong)]" />
<div class="post-detail__loading-shimmer h-4 w-[72%] rounded bg-[var(--site-panel-strong)]" />
</div>
<span class="sr-only">{{ isPostLoading ? '게시물을 불러오는 중입니다.' : '게시물을 표시할 수 없습니다.' }}</span>
</div>
</section>
</div>
</template>
<style scoped>
.post-detail__loading-shimmer {
position: relative;
overflow: hidden;
}
.post-detail__loading-shimmer::after {
position: absolute;
inset: 0;
content: '';
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.42), transparent);
transform: translateX(-100%);
animation: post-detail-loading-shimmer 1.2s ease-in-out infinite;
}
@keyframes post-detail-loading-shimmer {
to {
transform: translateX(100%);
}
}
</style>