feat: add loading skeleton for article content using Suspense
This commit is contained in:
@@ -2,6 +2,7 @@ import { getArticleBySlug } from '@/lib/feature/article/article.external';
|
||||
import { ArrowLeftIcon, CalendarIcon, ClockIcon } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { Suspense } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
|
||||
@@ -22,8 +23,48 @@ function formatDate(date: Date): string {
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
const ArticlePage = async ({ params }: ArticlePageProps) => {
|
||||
const { slug } = await params;
|
||||
const ArticleContentSkeleton = () => (
|
||||
<article className='w-full'>
|
||||
<div className='h-64 w-full animate-pulse bg-muted md:h-96' />
|
||||
|
||||
<div className='container mx-auto max-w-3xl px-4'>
|
||||
<div className='-mt-16 relative z-10'>
|
||||
<div className='mb-6 h-4 w-24 animate-pulse rounded bg-muted' />
|
||||
|
||||
<header className='mb-10'>
|
||||
<div className='mb-4 space-y-2'>
|
||||
<div className='h-8 w-full animate-pulse rounded bg-muted' />
|
||||
<div className='h-8 w-3/4 animate-pulse rounded bg-muted' />
|
||||
</div>
|
||||
|
||||
<div className='mb-5 space-y-2'>
|
||||
<div className='h-5 w-full animate-pulse rounded bg-muted' />
|
||||
<div className='h-5 w-2/3 animate-pulse rounded bg-muted' />
|
||||
</div>
|
||||
|
||||
<div className='flex gap-4'>
|
||||
<div className='h-3 w-28 animate-pulse rounded bg-muted' />
|
||||
<div className='h-3 w-16 animate-pulse rounded bg-muted' />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<hr className='mb-10 border-border' />
|
||||
|
||||
<div className='space-y-3 pb-16'>
|
||||
{[...Array(4)].map((_, i) => (
|
||||
<div key={i} className='space-y-2'>
|
||||
<div className='h-4 w-full animate-pulse rounded bg-muted' />
|
||||
<div className='h-4 w-full animate-pulse rounded bg-muted' />
|
||||
<div className='h-4 w-5/6 animate-pulse rounded bg-muted' />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
|
||||
const ArticleContent = async ({ slug }: { slug: string }) => {
|
||||
const article = await getArticleBySlug(slug);
|
||||
|
||||
if (!article) notFound();
|
||||
@@ -96,4 +137,14 @@ const ArticlePage = async ({ params }: ArticlePageProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const ArticlePage = async ({ params }: ArticlePageProps) => {
|
||||
const { slug } = await params;
|
||||
|
||||
return (
|
||||
<Suspense fallback={<ArticleContentSkeleton />}>
|
||||
<ArticleContent slug={slug} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
export default ArticlePage;
|
||||
|
||||
Reference in New Issue
Block a user