01
The problem
Tech news is unreadable. The volume per day is in the hundreds of thousands of articles globally, and most of it is noise, patch notes, gadget sales, recycled press releases, identical takes on the same Reuters wire. I wanted a feed that did the synthesis for me, scored each story by signal strength, and let me skim only what mattered across six regions. I also wanted it to read like editorial, in the register of Ben Thompson or The Economist.
02
How it fits together
03
What this is built on
- The Stratechery structure is the model: every piece is a verdict (the headline IS the thesis), follow the money first, name names, end with a falsifiable prediction. It became an explicit system prompt with banned phrases ('In a move that…', 'It's worth noting…') and required structures.
- NewsAPI's free tier sets the pipeline: 100 requests/day, language=en filter, 10 articles per query. That number rules out parallel fan-out across six regions, so the regions chain through a single daily run.
- Vercel's Hobby plan sets the constraints: crons run once per day max, edge functions time out at 60s. Those two limits shaped everything: chain-of-regions through a single cron, every region fits inside 60s, drop the slowest sub-pipelines.
- The provider's pricing tiers made Haiku the choice. Sonnet costs 3x as much. For an unattended synthesise-and-score pipeline, Haiku at one-third the cost is the right trade. The strict prompt carries the editorial voice.
- RSS (RFC 5005 and the general specs) is the entry point. Most regional tech publications publish RSS even if their websites are bad, Africa especially: TechCabal, Disrupt Africa, Iwacu. RSS was the cheapest way in.
04
What I couldn't do
- Free Vercel Hobby plan, one cron per day max, edge functions timeout in 60 seconds.
- Free NewsAPI tier, 100 requests/day total across the whole pipeline.
- Free Supabase tier, 500 MB DB, no concurrent connection pooling.
- LLM budget: tight. Every article generated had to be cheap or the system breaks at scale.
- Editorial voice has to be consistent, no boring summaries, no generic AI prose.
05
The decisions that shaped it
Decision 01
Chain-of-regions cron, not parallel fetch.
The single daily cron at 8 AM UTC hits `/api/generate-articles?region=global`. That route, once done, fires off `/api/generate-articles?region=africa` using a chain-secret header. Africa fires asia. Asia fires europe. Six regions, one cron entry, no fan-out, stays inside Vercel free-tier limits while still hitting every region daily. The chain is held together by a fire-and-forget fetch and a shared `x-chain-secret` env var. If one link breaks the chain dies silently, which is acceptable since the next day's run starts the chain over.
Decision 02
Slug-based de-duplication BEFORE the AI call.
NewsAPI returns the same headline from multiple sources (Reuters, AP, then 30 outlets quoting them). I slugify the title and check Supabase first, if the slug exists, skip the model entirely. Saves the LLM call on roughly 70% of what NewsAPI returns.
Decision 03
Edge runtime + direct fetch to the LLM provider, no SDK.
The an LLM Node SDK pulls in dependencies that don't run on Vercel Edge. I wrote a 30-line direct fetch to `the provider's messages endpoint` instead. Edge runtime keeps cold starts under 100ms (vs ~800ms for Node runtime), which matters when six chained route calls are racing the 60-second timeout. Also cuts deployment size.
Decision 04
Strict editorial system prompt with explicit banned phrases.
VANTAGE exists so articles read like editorial, not like a language model explaining the news. The system prompt is ~400 words of explicit constraints: every headline is a verdict, follow the money first, name names, no em-dashes (use commas/colons/periods only), no 'it's worth noting' / 'interestingly' / 'in a move that…', short paragraphs (2-3 sentences), end with a falsifiable prediction.
Decision 05
Use Haiku for the pipeline, not Sonnet.
Article synthesis is high-volume and low-stakes-per-token (the pipeline can re-generate tomorrow if today's output is mediocre). Haiku at 1/3 the cost lets the pipeline run sustainably on the budget I had. The strict editorial system prompt does the heavy lifting, Haiku follows it well enough for the format.
Decision 06
Region-by-region article cap (1 per region per run).
Originally tried to generate 3 articles per region per cron. Hit Vercel's 60s edge timeout repeatedly. Cut to 1 per region per run. Less content per day, but the cron completes.
Decision 07
Disable article deletion when credits run out, even though it bloats the feed.
Originally a nightly /api/expire cron deleted articles older than 48 hours to keep the feed fresh. When LLM credits exhausted, regeneration stopped, but expiration kept running. I'd wake up to a half-empty feed with no way to refill it. Disabled the expire cron, and the route now returns `{disabled: true}` even on manual trigger.
06
What broke and how I changed course
- First version stored articles forever. Quickly the feed had 800 articles and the page took 4 seconds to load. Added the /api/expire route to delete >48h. Then ran out of credits, disabled deletion. The feed now persists historic articles intentionally.
- Pipeline timeout was a recurring battle. First attempt with full prompt at 4000 tokens timed out. Trimmed prompt, then still timed out. Switched Haiku, then fitted. Then added Reddit + HN sub-pipelines, then timed out again. Dropped them. Final: lean RSS-only ingestion, 2500 tokens, Haiku, fits 60s. Half the iterations were add capability, blow timeout, remove capability.
- NEXT_PUBLIC_SITE_URL was hardcoded to a stale Vercel auto-URL (`vantage-three-chi.vercel.app`) that broke after a project rename. The chain-of-regions silently died because `fetch().catch(() => {})` swallowed the DNS error. No alarm fired; the feed just stopped updating regional content. Took me weeks to notice because global was still working.
- Found a stray `OneDrive/Desktop/FORGE/` folder accidentally committed inside the vantage repo (probably a Windows OneDrive sync mishap). Vercel build failed with a TypeScript error pointing into FORGE's `prisma.config.ts`. Removed the folder, added it to .gitignore.
07
What I didn't know, and how I learned
- I didn't fully understand SSE streaming when I started. Vercel Edge has different streaming semantics than Node. Spent a day debugging why chunks were buffering instead of arriving incrementally. Fix was using `controller.enqueue(encoder.encode(chunk))` instead of trying to write to the response body directly.
- I underestimated how aggressive Vercel's edge timeout is. 60s sounds like a lot until your pipeline does fetch, then JSON parse, then embedding lookup, then the model call, then DB insert per article. Learning to budget across the chain (under 10s per article, leave headroom for cold start) took a while.
- I learned the hard way that NewsAPI sorts by `relevancy` differently across regions. 'AI' in `global` returns major AI labs news. 'AI' in `africa` returns 80% Nigerian fintech. Ended up using region-specific keyword sets and RSS feeds for non-global regions to escape NewsAPI's relevancy bias.
08
What shipped
- 6 regions × 6 categories = 36 distinct content streams
- Single daily cron, fully autonomous, runs on Vercel free tier
- Articles scored 1-100 by signal strength
- Each article structured: headline (verdict), what happened, why it matters, who wins/loses, what to watch (with falsifiable prediction)
- Slug-based de-duplication before any AI call
- Edge runtime + direct the LLM provider fetch (no SDK overhead)
- Graceful degradation: when credits exhaust, articles persist instead of dying silently
09
What's next
I'd add an embedding-based 'find me articles like this one' next step. Right now the user picks a region and category. Semantic similarity would be more useful than topical buckets. The same approach from AYAT would work here.
What I learned
De-duplicating before the model call cut roughly 70% of the LLM spend. The free-tier constraints (60s timeout, 100 requests a day, 500 MB database) shaped the whole pipeline: chain-of-regions instead of parallel fetch, RSS instead of scraping, Haiku instead of Sonnet.