From 939b4034d9cb6a1ce7e2dfd1e427541cd2abe80c Mon Sep 17 00:00:00 2001 From: Alan Daniel Date: Tue, 28 Apr 2026 03:13:58 -0400 Subject: [PATCH] =?UTF-8?q?/pricing=20and=20/blog=20invisible=20to=20LLMs?= =?UTF-8?q?=20=E2=80=94=20fix=20SSR=20bailout=20(#45292)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? push client directive to leaf interactive components, keep content in server shell ## What is the current behavior? /pricing and blog pages are rendered it client side so crawlers don't get any content on fetch. Verifiy content with ```bash curl -s http://localhost:3000/pricing | sed 's/]*>.*<\/script>//g; s/]*>.*<\/style>//g; s/<[^>]*>//g' | wc -w curl -s http://localhost:3000/blog | sed 's/]*>.*<\/script>//g; s/]*>.*<\/style>//g; s/<[^>]*>//g' | wc -w curl -s http://localhost:3000/blog/ | sed 's/]*>.*<\/script>//g; s/]*>.*<\/style>//g; s/<[^>]*>//g' | wc -w ``` ## Summary by CodeRabbit * **New Features** * Featured blog post now displays prominently on the blog landing page * Added smooth anchor link navigation on the pricing page * Improved table of contents tracking with scroll-to-anchor behavior on blog posts * Enhanced pricing comparison and plans sections with better data integration --- apps/www/app/blog/BlogClient.tsx | 143 ++++++++---------- .../app/blog/[slug]/BlogPostAnchorEffect.tsx | 10 ++ apps/www/app/blog/[slug]/BlogPostClient.tsx | 35 ++--- apps/www/app/blog/page.tsx | 28 +++- apps/www/app/pricing/HashAnchorScroll.tsx | 29 ++++ .../app/pricing/PricingComparisonSection.tsx | 16 ++ apps/www/app/pricing/PricingContent.tsx | 109 ++----------- apps/www/app/pricing/PricingPlansSection.tsx | 72 +++++++++ apps/www/components/Panel/Panel.tsx | 8 +- .../Pricing/PricingComputeSection.tsx | 2 + apps/www/components/Pricing/PricingFAQs.tsx | 2 + 11 files changed, 249 insertions(+), 205 deletions(-) create mode 100644 apps/www/app/blog/[slug]/BlogPostAnchorEffect.tsx create mode 100644 apps/www/app/pricing/HashAnchorScroll.tsx create mode 100644 apps/www/app/pricing/PricingComparisonSection.tsx create mode 100644 apps/www/app/pricing/PricingPlansSection.tsx diff --git a/apps/www/app/blog/BlogClient.tsx b/apps/www/app/blog/BlogClient.tsx index 636cefd9ed6..953b2da92c0 100644 --- a/apps/www/app/blog/BlogClient.tsx +++ b/apps/www/app/blog/BlogClient.tsx @@ -1,16 +1,13 @@ 'use client' -import { useState, useCallback } from 'react' -import DefaultLayout from 'components/Layouts/Default' +import { isBrowser, LOCAL_STORAGE_KEYS } from 'common' +import BlogFilters from 'components/Blog/BlogFilters' import BlogGridItem from 'components/Blog/BlogGridItem' import BlogListItem from 'components/Blog/BlogListItem' -import BlogFilters from 'components/Blog/BlogFilters' -import FeaturedThumb from 'components/Blog/FeaturedThumb' -import { cn } from 'ui' -import { LOCAL_STORAGE_KEYS, isBrowser } from 'common' import { useInfiniteScrollWithFetch } from 'hooks/useInfiniteScroll' - +import { useCallback, useState } from 'react' import type PostTypes from 'types/post' +import { cn } from 'ui' export type BlogView = 'list' | 'grid' @@ -74,14 +71,20 @@ export default function BlogClient({ initialBlogs, totalPosts }: BlogClientProps const [filteredTotal, setFilteredTotal] = useState(null) const isList = view === 'list' - // Determine which posts and total to use based on filter state const currentPosts = filteredPosts ?? initialBlogs const currentTotal = filteredTotal ?? totalPosts const fetchMorePosts = useCallback( async (offset: number, limit: number) => { + const isFiltered = + (filterParams.category && filterParams.category !== 'all') || Boolean(filterParams.search) + // The featured post is rendered above the list (not in `items`), so the + // API offset has to skip past it for unfiltered fetches. Filtered + // results come from a separate query and don't share that hero slot. + const apiOffset = isFiltered ? offset : offset + 1 + const params = new URLSearchParams({ - offset: offset.toString(), + offset: apiOffset.toString(), limit: limit.toString(), }) @@ -118,9 +121,7 @@ export default function BlogClient({ initialBlogs, totalPosts }: BlogClientProps rootMargin: '1000px', }) - // Handle filter changes - fetch filtered results from API const handleFilterChange = useCallback(async (category?: string, search?: string) => { - // If no filters, reset to initial state if ((!category || category === 'all') && !search) { setFilterParams({}) setFilteredPosts(null) @@ -159,78 +160,64 @@ export default function BlogClient({ initialBlogs, totalPosts }: BlogClientProps } }, []) - const featuredPost = initialBlogs[0] - return ( <> - -

Supabase blog

-
- {featuredPost && } -
+ -
-
- - -
    - {isFiltering ? ( - // Show skeletons while filtering - isList ? ( - Array.from({ length: SKELETON_COUNT }).map((_, idx) => ( -
    - -
    - )) - ) : ( - Array.from({ length: SKELETON_COUNT }).map((_, idx) => ( -
    - -
    - )) - ) - ) : blogs?.length ? ( - blogs?.map((blog: PostTypes, idx: number) => - isList ? ( -
    - -
    - ) : ( -
    - -
    - ) - ) - ) : ( -

    No results

    - )} -
- - {hasMore && !isFiltering && ( -