Skip to content

Commit 983c646

Browse files
committed
feat(blog): implement streaming blog posts with enhanced loading and error handling
- Added StreamingBlogGrid component for dynamic loading of blog posts. - Updated BlogPage to utilize StreamingBlogGrid with a loading state. - Refactored API calls to support pagination and error handling. - Introduced BlogCardSkeleton for improved user experience during loading. - Enhanced blog post fetching logic in the API layer for better performance. - Updated metadata generation for blog posts to handle errors gracefully. - Added new utility functions for blog data handling and validation.
1 parent de0f2d7 commit 983c646

21 files changed

Lines changed: 1067 additions & 718 deletions

File tree

app/(landing)/blog/[slug]/page.tsx

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,21 @@ import React from 'react';
22
import { Metadata } from 'next';
33
import { notFound } from 'next/navigation';
44
import BlogPostDetails from '../../../../components/landing-page/blog/BlogPostDetails';
5-
import { getBlogPost, getAllBlogPosts } from '@/lib/data/blog';
5+
import { getBlogPost, getBlogPosts } from '@/lib/api/blog';
66
import { generateBlogPostMetadata } from '@/lib/metadata';
77

88
export async function generateStaticParams() {
9-
const posts = await getAllBlogPosts();
9+
try {
10+
// For static generation, we'll need to fetch all posts
11+
// This might need to be adjusted based on your backend implementation
12+
const { posts } = await getBlogPosts({ page: 1, limit: 50 });
1013

11-
return posts.map(post => ({
12-
slug: post.slug,
13-
}));
14+
return posts.map(post => ({
15+
slug: post.slug,
16+
}));
17+
} catch {
18+
return [];
19+
}
1420
}
1521

1622
export async function generateMetadata({
@@ -19,16 +25,24 @@ export async function generateMetadata({
1925
params: Promise<{ slug: string }>;
2026
}): Promise<Metadata> {
2127
const { slug } = await params;
22-
const post = await getBlogPost(slug);
2328

24-
if (!post) {
29+
try {
30+
const post = await getBlogPost(slug);
31+
32+
if (!post) {
33+
return {
34+
title: 'Blog Post Not Found',
35+
description: 'The requested blog post could not be found.',
36+
};
37+
}
38+
39+
return generateBlogPostMetadata(post);
40+
} catch {
2541
return {
2642
title: 'Blog Post Not Found',
2743
description: 'The requested blog post could not be found.',
2844
};
2945
}
30-
31-
return generateBlogPostMetadata(post);
3246
}
3347

3448
const BlogPostPage = async ({
@@ -37,13 +51,18 @@ const BlogPostPage = async ({
3751
params: Promise<{ slug: string }>;
3852
}) => {
3953
const { slug } = await params;
40-
const post = await getBlogPost(slug);
4154

42-
if (!post) {
55+
try {
56+
const post = await getBlogPost(slug);
57+
58+
if (!post) {
59+
notFound();
60+
}
61+
62+
return <BlogPostDetails post={post} />;
63+
} catch {
4364
notFound();
4465
}
45-
46-
return <BlogPostDetails post={post} />;
4766
};
4867

4968
export default BlogPostPage;

app/(landing)/blog/page.tsx

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,73 @@
1-
import React from 'react';
1+
import React, { Suspense } from 'react';
22
import { Metadata } from 'next';
33
import { generatePageMetadata } from '@/lib/metadata';
4-
import BlogGrid from '@/components/landing-page/blog/BlogGrid';
5-
import { getAllBlogPosts } from '@/lib/data/blog';
4+
import StreamingBlogGrid from '@/components/landing-page/blog/StreamingBlogGrid';
5+
import { getBlogPosts } from '@/lib/api/blog';
66
import TestimonialSection from '@/components/testimonials/TestimonialsSection';
77
import { testimonials } from '@/components/testimonials/data/testimonial';
8+
import { Skeleton } from '@/components/ui/skeleton';
9+
import BlogCardSkeleton from '@/components/landing-page/blog/BlogCardSkeleton';
810

911
export const metadata: Metadata = generatePageMetadata('blog');
1012

11-
const BlogPage = async () => {
12-
const posts = await getAllBlogPosts();
13+
async function StreamingBlogGridWrapper() {
14+
try {
15+
const { posts, hasMore, total } = await getBlogPosts({
16+
page: 1,
17+
limit: 12,
18+
sort: 'latest',
19+
});
20+
21+
return (
22+
<StreamingBlogGrid
23+
initialPosts={posts}
24+
totalPosts={total}
25+
hasMore={hasMore}
26+
initialPage={1}
27+
/>
28+
);
29+
} catch {
30+
return (
31+
<StreamingBlogGrid
32+
initialPosts={[]}
33+
totalPosts={0}
34+
hasMore={false}
35+
initialPage={1}
36+
/>
37+
);
38+
}
39+
}
40+
41+
function BlogGridLoading() {
42+
return (
43+
<div className='min-h-screen bg-[#030303]'>
44+
<div className='mx-auto max-w-6xl px-6 py-8'>
45+
<div className='flex gap-3 md:flex-row md:items-center md:justify-between lg:gap-16'>
46+
<div className='flex items-center gap-3'>
47+
<Skeleton className='h-10 w-20 rounded-lg' />
48+
<Skeleton className='h-10 w-24 rounded-lg' />
49+
</div>
50+
<Skeleton className='h-10 w-full rounded-lg md:min-w-[300px]' />
51+
</div>
52+
</div>
1353

54+
<div className='mx-auto max-w-6xl px-6 py-12'>
55+
<div className='grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3'>
56+
{Array.from({ length: 6 }).map((_, i) => (
57+
<BlogCardSkeleton key={i} />
58+
))}
59+
</div>
60+
</div>
61+
</div>
62+
);
63+
}
64+
65+
const BlogPage = async () => {
1466
return (
1567
<div className='min-h-screen bg-[#030303]'>
16-
<BlogGrid posts={posts} showLoadMore={true} />
68+
<Suspense fallback={<BlogGridLoading />}>
69+
<StreamingBlogGridWrapper />
70+
</Suspense>
1771
<TestimonialSection testimonials={testimonials} />
1872
</div>
1973
);

app/(landing)/projects/page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable no-console */
12
'use client';
23

34
import React from 'react';

app/api/blog/posts/route.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { getBlogPostsStreaming } from '@/lib/data/blog';
3+
4+
export async function GET(request: NextRequest) {
5+
try {
6+
const { searchParams } = new URL(request.url);
7+
const page = parseInt(searchParams.get('page') || '1');
8+
const limit = parseInt(searchParams.get('limit') || '12');
9+
10+
const result = await getBlogPostsStreaming(page, limit);
11+
12+
return NextResponse.json(result);
13+
} catch {
14+
return NextResponse.json(
15+
{ error: 'Failed to fetch blog posts' },
16+
{ status: 500 }
17+
);
18+
}
19+
}

0 commit comments

Comments
 (0)