Industry News: server-render initial feed posts (fix 0 articles on load)
This commit is contained in:
@@ -53,13 +53,18 @@ function parseArticleDate(dateStr: string): Date | null {
|
|||||||
return isNaN(d.getTime()) ? null : d;
|
return isNaN(d.getTime()) ? null : d;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ArticleFeed() {
|
type ArticleFeedProps = {
|
||||||
|
initialPosts?: ArticleItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ArticleFeed({ initialPosts = [] }: ArticleFeedProps) {
|
||||||
|
const hasInitialPosts = initialPosts.length > 0;
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [activeCategory, setActiveCategory] = useState("All");
|
const [activeCategory, setActiveCategory] = useState("All");
|
||||||
const [sourceFilter, setSourceFilter] = useState<SourceFilter>("All");
|
const [sourceFilter, setSourceFilter] = useState<SourceFilter>("All");
|
||||||
const [wpPosts, setWpPosts] = useState<ArticleItem[]>([]);
|
const [wpPosts, setWpPosts] = useState<ArticleItem[]>(initialPosts);
|
||||||
const [loadingWp, setLoadingWp] = useState(true);
|
const [loadingWp, setLoadingWp] = useState(!hasInitialPosts);
|
||||||
const [feedMode, setFeedMode] = useState<FeedMode>("pagination");
|
const [feedMode, setFeedMode] = useState<FeedMode>("pagination");
|
||||||
const [infiniteCount, setInfiniteCount] = useState(ARTICLES_PER_PAGE);
|
const [infiniteCount, setInfiniteCount] = useState(ARTICLES_PER_PAGE);
|
||||||
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
||||||
@@ -89,8 +94,9 @@ export default function ArticleFeed() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Fetch imported WordPress posts for the feed
|
// Client fetch only when the server did not hydrate initial posts.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (hasInitialPosts) return;
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -108,7 +114,7 @@ export default function ArticleFeed() {
|
|||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
};
|
};
|
||||||
}, []);
|
}, [hasInitialPosts]);
|
||||||
|
|
||||||
const handleFeedModeChange = (mode: FeedMode) => {
|
const handleFeedModeChange = (mode: FeedMode) => {
|
||||||
setFeedMode(mode);
|
setFeedMode(mode);
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import FeaturedCarouselServer from '@/components/FeaturedCarouselServer';
|
|||||||
import FeaturedBento from "./components/FeaturedBento";
|
import FeaturedBento from "./components/FeaturedBento";
|
||||||
import SpotlightCarousel from "./components/SpotlightCarousel";
|
import SpotlightCarousel from "./components/SpotlightCarousel";
|
||||||
import ArticleFeed from "./components/ArticleFeed";
|
import ArticleFeed from "./components/ArticleFeed";
|
||||||
|
import { getLatestImportedArticles } from "@/lib/articles/legacy-source";
|
||||||
|
import { decorateHomepageFeed } from "@/lib/promoted-articles";
|
||||||
import ScrollRevealSection from "@/components/ScrollRevealSection";
|
import ScrollRevealSection from "@/components/ScrollRevealSection";
|
||||||
import NewsletterSignup from "./components/NewsletterSignup";
|
import NewsletterSignup from "./components/NewsletterSignup";
|
||||||
import SponsorLogoStrip from "@/components/SponsorLogoStrip";
|
import SponsorLogoStrip from "@/components/SponsorLogoStrip";
|
||||||
@@ -137,7 +139,32 @@ function FeedSkeleton() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function HomePage() {
|
const HOME_FEED_LIMIT = 5000;
|
||||||
|
|
||||||
|
async function loadHomeFeedPosts() {
|
||||||
|
const { items } = await getLatestImportedArticles(HOME_FEED_LIMIT, 0);
|
||||||
|
const base = items.map((a) => ({
|
||||||
|
title: a.title,
|
||||||
|
excerpt: a.excerpt,
|
||||||
|
category: a.category,
|
||||||
|
date: a.date,
|
||||||
|
author: a.author,
|
||||||
|
authorSlug: a.authorSlug,
|
||||||
|
slug: a.slug,
|
||||||
|
image: a.image,
|
||||||
|
alt: a.alt,
|
||||||
|
source: "imported" as const,
|
||||||
|
}));
|
||||||
|
const decorated = await decorateHomepageFeed("avbeat", base);
|
||||||
|
return decorated.map((d) => ({
|
||||||
|
...d.article,
|
||||||
|
promoted_kind: d.promoted_kind,
|
||||||
|
promoted_advertiser: d.promoted_advertiser || null,
|
||||||
|
promoted_until: d.promoted_until || null,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function HomePage() {
|
||||||
// Hard opt-out of all caching for this route. Without this, a child
|
// Hard opt-out of all caching for this route. Without this, a child
|
||||||
// server component (FeaturedBentoFromDb -> pickAds -> fetch in lib/ads.ts
|
// server component (FeaturedBentoFromDb -> pickAds -> fetch in lib/ads.ts
|
||||||
// with `next: { revalidate: 30 }`) pulls the whole page into 30-second
|
// with `next: { revalidate: 30 }`) pulls the whole page into 30-second
|
||||||
@@ -146,6 +173,7 @@ export default function HomePage() {
|
|||||||
// is the unambiguous, child-tree-overriding opt-out and is what the
|
// is the unambiguous, child-tree-overriding opt-out and is what the
|
||||||
// hero swap needs to render fresh on every request.
|
// hero swap needs to render fresh on every request.
|
||||||
noStore();
|
noStore();
|
||||||
|
const initialFeedPosts = await loadHomeFeedPosts();
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<div className="min-h-screen bg-background">
|
||||||
{/* Sticky top bar + nav */}
|
{/* Sticky top bar + nav */}
|
||||||
@@ -174,7 +202,7 @@ export default function HomePage() {
|
|||||||
{/* Article feed + sidebar */}
|
{/* Article feed + sidebar */}
|
||||||
<ScrollRevealSection id="the-latest" className="section-enter section-enter-5 scroll-mt-32" delay={2}>
|
<ScrollRevealSection id="the-latest" className="section-enter section-enter-5 scroll-mt-32" delay={2}>
|
||||||
<Suspense fallback={<FeedSkeleton />}>
|
<Suspense fallback={<FeedSkeleton />}>
|
||||||
<ArticleFeed />
|
<ArticleFeed initialPosts={initialFeedPosts} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</ScrollRevealSection>
|
</ScrollRevealSection>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user