Notes

Field notes

Short, opinionated takes from the work — patterns that paid off, things I'd do differently next time, and questions I'm still chewing on.
Nov 2025Distributed SystemsNode.js

Why I reached for BullMQ over RabbitMQ for background jobs

When the Marketpal lead-distribution rewrite needed a queue, the team's instinct was RabbitMQ — proven, language-agnostic, has every feature ever invented. I pushed for BullMQ instead. The reasoning, in order of importance:

One language across producer + consumer.The whole platform is Node/TypeScript. RabbitMQ would've added a second runtime model (AMQP mental model) on top. BullMQ is just a Redis client with typed Job payloads — same TS types flow end to end.

Redis was already there.We had Redis for caching and session state. Adding RabbitMQ meant a new server, new ops surface, new monitoring. BullMQ piggybacks on what's already in production.

Trade-off I accepted: BullMQ is less robust under network partition than RabbitMQ. For our throughput (≤ 200 jobs/min, no cross-region requirements), the simplicity won.

Oct 2025AIRAG

Three RAG retrieval gotchas no one writes about

Most RAG tutorials end at “embed your docs, store in a vector DB, cosine-similarity search.” That's the easy 80%. The remaining 20% is where every prod system I've seen gets bitten:

1. Chunk overlap eats your retrieval quality. Naive splitting on token count produces chunks that cut mid-sentence. The model retrieves chunk N+1 but loses the context from chunk N. Use a recursive splitter with ~15% overlap — the duplicate tokens are worth it.

2. Cosine similarity doesn't mean “relevant.” A query about “refund policy” will pull every chunk with the word “refund” ahead of an actual relevant policy summary. Re-ranking with a cross-encoder (or just an LLM call) fixes 90% of relevance issues with retrieval.

3. Most failures are query-side, not corpus-side. Spend a day on query expansion / HyDE before adding more docs.

Sep 2025Next.jsPerformance

When SSR isn't worth the complexity

The Next.js docs make SSR feel like the default for everything. After three projects, my heuristic is the opposite — start static, opt into SSR only when you have specific evidence you need it.

Static handles 80% of cases. Marketing pages, dashboards with client-fetched data, dashboards behind auth — none of them need SSR. They need a fast first paint, which static prerender + client hydration does better than SSR.

SSR earns its complexity when:SEO depends on data that changes per request (search pages, location-targeted content), or authenticated content needs to render server-side (paid content gates). That's a real list — short, specific.

The wins from default-SSR are smaller than they look on the docs page. The costs (slower builds, more failure modes, cache complexity) are real.