Architecture · · 4 min read · Updated
Architecting Zivvi: Hybrid Search & Traffic Rankings in Next.js 16
How I built zivvi.app to index 2,400+ web apps using daily Tranco 1M traffic deltas, Upstash Vector + Postgres hybrid search, and Next.js 16 Partial Prerendering.
Zayne Lovecraft
Software Engineer III

Launch directories have a structural flaw: rankings spike for 24 hours when a founder rallies upvotes, then freeze into a static graveyard. Meanwhile, traditional B2B review portals rank software by whoever pays for the top sponsored slot.
I built Zivvi (case study) to solve both problems at the data layer. Zivvi indexes over 2,400 web applications across 28 categories, separating objective global internet traffic from first-party community engagement and serving the entire catalog through Next.js 16 Partial Prerendering (PPR).
Here is how the ranking pipeline, hybrid search engine, and caching architecture work under the hood.
1. Separating global traffic from launch-day hype
If you put a brand-new indie SaaS tool and a global platform like Notion or Linear into a single leaderboard, either the indie app is invisible or the global rankings look fake. Zivvi splits discovery into distinct, verifiable signals:
- Global Web Rank (`/popular`): Every night, a scheduled cron job ingests the full Tranco top-1M domain list (which aggregates 30-day traffic data from Chrome UX Report, Cloudflare Radar, Cisco Umbrella, and Majestic). Each published app's canonical hostname is matched against the daily snapshot to record
web_rankand a rolling 28-day baseline (web_rank_28d). - 7-Day Momentum (`/trending`): Instead of raw traffic volume, trending scores combine 7-day first-party velocity—upvotes ($\times 3$), verified reviews ($\times 5$), and unique outbound clicks through
/go/[slug]—with 28-day domain rank climbs ($\Delta = \text{web_rank_28d} - \text{web_rank}$). - Cooling Off & Rising Leaders: Because we store daily rank time-series in Postgres, Zivvi also surfaces which established products are gaining or losing global web share over a 28-day window.
One non-obvious lesson from ingesting raw Tranco data: machine traffic looks like popularity if you don't filter it. Ad-tech SSPs, telemetry endpoints, NTP pools, and CDN domains rank in the global top 5,000 purely from background SDK pings. We maintain an explicit domain skip-list and hostname normalizer so only real, user-facing web applications enter the catalog.
2. Hybrid search: fusing Upstash Vector with Postgres FTS and pg_trgm
Users search an app store in two completely different ways:
- Lexical lookup: "supabase", "linear", or a typo like "posthogg".
- Intent lookup: "offline AI assistant that runs locally on iPhone" or "academic research workspace with LaTeX citations".
Pure vector search is mediocre at exact prefix and typo matching, while Postgres full-text search (tsvector) misses semantic paraphrases. Zivvi runs both in parallel and fuses the results:
// Parallel lexical (Postgres RPC) + semantic (Upstash Vector) retrieval
const [lexicalResult, vectorHits] = await Promise.all([
supabase.schema("zivvi").rpc("search_products", {
p_query: query,
p_category: categorySlug ?? null,
p_pricing: pricing ?? null,
p_limit: 40,
}),
queryVectorIndex(query, { topK: 30 }),
]);
On the Postgres side, zivvi.search_products combines:
- Weighted
tsvectorranking acrossname(A),tagline(B),tags(B), anddescription(C). - Trigram similarity (
pg_trgm) on product names and slugs so typos still resolve in a single round-trip. - Category authority boosting so canonical leaders in a category outrank tangential keyword matches.
When an admin approves a new submission or edits a listing in /admin, a Next.js after() callback re-embeds the product (text-embedding-3-small) and upserts its vector into Upstash without blocking the moderation response.
3. Next.js 16.4 Cache Components and Partial Prerendering (PPR)
With 2,400+ product pages (/apps/[slug]), alternatives hubs (/alternatives/[slug]), stack pages (/built-with/[tool]), and category leaderboards (/categories/[slug]), building every permutation statically at deploy time doesn't scale—and rendering every request dynamically hurts Time to First Byte (TTFB).
Zivvi runs on Next.js 16.4 with cacheComponents: true and partialPrefetching: true. As I covered in Understanding Client Components and Client Boundaries in Next.js, keeping the route shell deterministic lets Next.js stream an instant static shell (ShellStatic) on link hover while resolving dynamic data inside <Suspense> boundaries:
- 5-minute minimum shell stale time (
MIN_SHELL_STALE = 300): In Next.js 16.4, any"use cache"function with astalewindow under 300 seconds is excluded from PPR app shells. All catalog read RPCs cache withcacheTaginvalidation (product:{slug},products,categories) so mutations in/adminor user upvotes callupdateTag()for immediate consistency. - Zero-layout-shift streaming: Dynamic
[slug]routes wrapawait paramsin a<Suspense>boundary with a geometry-matched skeleton so forward and back navigation feel instantaneous.
4. Programmatic SEO & agent-readable endpoints
Every listing on Zivvi participates in a connected entity graph—similar to the patterns in Programmatic Technical SEO: Internal Links & Sitemaps at Scale and Generative Engine Optimization (GEO) & CWV Gates in Next.js:
- Segmented sitemap index (
/sitemap.xml): Splits URLs into child sitemaps (pages,apps,categories,blog,hubs) so Google Search Console reports indexation coverage per content type. - Semantic alternatives (
/alternatives/[slug]): Combines category peers, shared stack tags, and vector similarity to generate clean, zero-fluff comparison pages. - Maker & AI interfaces: Verified makers can claim unclaimed community nominations, link public GitHub repositories for live star/fork telemetry, and embed dynamic SVG upvote badges (
/badge/[slug]). For AI agents and answer engines, Zivvi exposes/llms.txtand a public JSON API at/api/v1/apps.
Takeaway
Building a modern directory that compounds organically requires treating rankings as a data engineering problem rather than a popularity contest. You can explore the live rankings at zivvi.app, browse the global traffic leaderboard, or read the full Zivvi architecture case study.
- Next.js 16
- Supabase
- Upstash Vector
- Technical SEO
- Search Architecture
Keep reading

AI · · 7 min read
Top 10 AI Tools to Build With Today (and How to Use Them)
The 10 AI tools I actually build with, from Cursor and Claude to the AI SDK, Vercel Workflow, and MCP, and how they fit into real automation pipelines.

Technical SEO · · 2 min read
Generative Engine Optimization (GEO) & CWV Gates in Next.js
How to audit Next.js apps for AI search citations (JSON-LD entity graphs, llms.txt, bot rules) and enforce Core Web Vitals client bundle budgets in CI.