From d8b64f1006000deef38ce447f875b978c19009a2 Mon Sep 17 00:00:00 2001 From: gusmar2017 Date: Mon, 13 Jul 2026 18:10:34 -0500 Subject: [PATCH] feat: internal linking hubs for gated servers (indexing recovery slice 4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ensures every isIndexable() (Slice 2 gated) server is reachable in <=3 clicks from the homepage: - Category hubs (/categories/[category]) now link the FULL gated set per category via new getIndexableServersByCategory(), not the old 200-row raw-active-servers query. - New /categories index page (crawlable hub-of-hubs; fills the previously-dangling breadcrumb target referenced by generateCategoryJsonLd). - RelatedServersForCategory now sources from the gated query and is wired onto every server detail page (previously only used on category hubs and blog posts) — self-excludes the current server, category-scoped. - Homepage "Featured Servers" now uses new getIndexableTopServers() instead of the unfiltered getTopServers(), so the top-N-by-stars linking surface never resurfaces a thin/non-gated page. - /servers pagination confirmed already SSR-crawlable (real next/link hrefs via PaginationLink, passed through the client filter wrapper as children) — documented with a comment, no behavior change needed. - Homepage footer links to /categories in two places for redundant crawl paths. Click-depth: home -> category hub (1) -> server (2); home -> Featured Servers top-6 (1); home -> /servers paginated, stars-desc default fronts the gated cluster (2-3). Category-less gated servers fall back to the /servers and Featured Servers paths. Co-Authored-By: Claude Fable 5 --- apps/web/app/categories/[category]/page.tsx | 14 +- apps/web/app/categories/page.tsx | 144 ++++++++++++++++++ apps/web/app/page.tsx | 24 ++- apps/web/app/servers/[slug]/page.tsx | 49 ++---- apps/web/app/servers/page.tsx | 10 +- .../components/RelatedServersForCategory.tsx | 22 ++- apps/web/lib/queries.ts | 85 +++++++++++ 7 files changed, 300 insertions(+), 48 deletions(-) create mode 100644 apps/web/app/categories/page.tsx diff --git a/apps/web/app/categories/[category]/page.tsx b/apps/web/app/categories/[category]/page.tsx index 3cb8884..db5e876 100644 --- a/apps/web/app/categories/[category]/page.tsx +++ b/apps/web/app/categories/[category]/page.tsx @@ -1,4 +1,4 @@ -import { getServersByCategory, getCategoryCount } from '@/lib/queries'; +import { getIndexableServersByCategory, getCategoryCount } from '@/lib/queries'; import { generateCategoryMetadata, generateCategoryJsonLd } from '@/lib/metadata'; import { getQualityStatus } from '@/lib/quality-status'; import { safeJsonLd } from '@/lib/json-ld'; @@ -45,10 +45,14 @@ export default async function CategoryPage({ if (!(CATEGORIES as readonly string[]).includes(category)) notFound(); const label = CATEGORY_LABELS[category as keyof typeof CATEGORY_LABELS] || category; - // Fetch servers (up to 200 for display) and the true total count in parallel. - // The true count feeds JSON-LD numberOfItems; servers feeds the card grid. + // Indexing-recovery Slice 4: this hub must link every isIndexable() (gated) + // server in the category — getIndexableServersByCategory() is the full, + // uncapped, pre-filtered set (not getServersByCategory()'s 200-row raw + // cap), so a large category can never silently drop gated servers from + // its own hub page. categoryCount (JSON-LD numberOfItems) intentionally + // still reflects the true active-server count, not just the gated subset. const [servers, categoryCount] = await Promise.all([ - getServersByCategory(category), + getIndexableServersByCategory(category), getCategoryCount(category), ]); @@ -90,7 +94,7 @@ export default async function CategoryPage({

- {categoryCount} servers in this category + {servers.length} servers in this category