Industry News: server-render initial feed posts (fix 0 articles on load)

This commit is contained in:
2026-06-26 19:45:38 +00:00
parent 7d41eebf62
commit ceb5333077
2 changed files with 41 additions and 7 deletions

View File

@@ -53,13 +53,18 @@ function parseArticleDate(dateStr: string): Date | null {
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 [searchQuery, setSearchQuery] = useState("");
const [activeCategory, setActiveCategory] = useState("All");
const [sourceFilter, setSourceFilter] = useState<SourceFilter>("All");
const [wpPosts, setWpPosts] = useState<ArticleItem[]>([]);
const [loadingWp, setLoadingWp] = useState(true);
const [wpPosts, setWpPosts] = useState<ArticleItem[]>(initialPosts);
const [loadingWp, setLoadingWp] = useState(!hasInitialPosts);
const [feedMode, setFeedMode] = useState<FeedMode>("pagination");
const [infiniteCount, setInfiniteCount] = useState(ARTICLES_PER_PAGE);
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(() => {
if (hasInitialPosts) return;
let cancelled = false;
(async () => {
try {
@@ -108,7 +114,7 @@ export default function ArticleFeed() {
return () => {
cancelled = true;
};
}, []);
}, [hasInitialPosts]);
const handleFeedModeChange = (mode: FeedMode) => {
setFeedMode(mode);

View File

@@ -8,6 +8,8 @@ import FeaturedCarouselServer from '@/components/FeaturedCarouselServer';
import FeaturedBento from "./components/FeaturedBento";
import SpotlightCarousel from "./components/SpotlightCarousel";
import ArticleFeed from "./components/ArticleFeed";
import { getLatestImportedArticles } from "@/lib/articles/legacy-source";
import { decorateHomepageFeed } from "@/lib/promoted-articles";
import ScrollRevealSection from "@/components/ScrollRevealSection";
import NewsletterSignup from "./components/NewsletterSignup";
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
// server component (FeaturedBentoFromDb -> pickAds -> fetch in lib/ads.ts
// 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
// hero swap needs to render fresh on every request.
noStore();
const initialFeedPosts = await loadHomeFeedPosts();
return (
<div className="min-h-screen bg-background">
{/* Sticky top bar + nav */}
@@ -174,7 +202,7 @@ export default function HomePage() {
{/* Article feed + sidebar */}
<ScrollRevealSection id="the-latest" className="section-enter section-enter-5 scroll-mt-32" delay={2}>
<Suspense fallback={<FeedSkeleton />}>
<ArticleFeed />
<ArticleFeed initialPosts={initialFeedPosts} />
</Suspense>
</ScrollRevealSection>