diff --git a/src/app/home-page/components/ArticleFeed.tsx b/src/app/home-page/components/ArticleFeed.tsx index 5368e8b..a045870 100644 --- a/src/app/home-page/components/ArticleFeed.tsx +++ b/src/app/home-page/components/ArticleFeed.tsx @@ -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("All"); - const [wpPosts, setWpPosts] = useState([]); - const [loadingWp, setLoadingWp] = useState(true); + const [wpPosts, setWpPosts] = useState(initialPosts); + const [loadingWp, setLoadingWp] = useState(!hasInitialPosts); const [feedMode, setFeedMode] = useState("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); diff --git a/src/app/home-page/page.tsx b/src/app/home-page/page.tsx index 7cd3be7..0fa8751 100644 --- a/src/app/home-page/page.tsx +++ b/src/app/home-page/page.tsx @@ -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 (
{/* Sticky top bar + nav */} @@ -174,7 +202,7 @@ export default function HomePage() { {/* Article feed + sidebar */} }> - +