← All work

Founder & sole engineer (Software Is Nothing, LLC) · 2024–present

tailharbor.eu

A European pet-adoption platform spanning 40 countries and 16 UI languages — full-stack web, native iOS + Android apps, and a nearly-4,000-adapter scraper system feeding ~132K live listings, ~3,900 shelters, and 700K+ managed photos.

Next.js 16ExpressPrismaPostgresMeilisearchBullMQReact NativeExpo EASPuppeteerClaude

What it is

A consumer-facing pet-adoption aggregator covering 40 European countries in 16 languages. It pulls listings from a long tail of rescue and shelter sites, normalises the data, scores photos, translates descriptions, and serves a unified search experience at tailharbor.eu — on the web and on native iOS and Android apps. Built and run solo.

Stack

  • Web: Next.js 16 (App Router, server components, ISR), Clerk for auth, full UI in 16 locales (EN / DE / FR / ES / IT / NL / PT / PL / RO / SV / EL / HU / CS / DA / FI / NO).
  • API: Express + Prisma ORM on PostgreSQL 16 with PostGIS, behind PgBouncer, with a Redis cache. The homepage mixed-sort query was cut from 10,610 ms to 89 ms (~120×) by moving it to a materialized view refreshed every 5 minutes.
  • Search: Meilisearch (migrated off Elasticsearch in 2026), with typo-tolerance disabled on breed fields so exact breed names don't fuzzy-match.
  • Scraper system: a BullMQ job queue driving close to 4,000 site-specific adapters. Cheerio for static HTML, Puppeteer for JS-rendered sites (pooled browser with [puppeteer-pool] telemetry that warns at p95 > 5 s). WP REST, Wix data APIs, and ACF endpoints used where exposed; a proxy layer for IP-blocked targets.
  • Mobile: React Native via Expo EAS — native iOS and Android apps in TestFlight / Play internal testing, with dozens of builds shipped via a one-command ship-both.sh parallel pipeline (auto-bump versioning, preflight checks, eas submit) that reaches both stores in ~8 min wall-clock. Full 16-locale parity with the web app.
  • Storage: Cloudflare R2 for animal photos (700,000+ photos under lifecycle management: stamp-on-sight + stale cleanup + null-stored retry, all gated by an off → observe → dry-run → enforce mode flag).
  • Payments: Stripe (freemium → PRO).
  • AI: Anthropic Claude for breed/colour/size enrichment (runtime translation runs on local open-weight models, not a paid API). Both photo aesthetic scoring and face detection were moved off paid cloud AI onto local on-device Apple Vision inference — lifting aesthetic-score coverage from ~3% to ~96% of photos while removing the per-call cost.
  • Deploy: Docker Compose on a Hetzner box, with a local ci-local.sh + pre-push hook that mirrors CI so a billed pipeline is never load-bearing.
  • Compounding layer: a Recipe DB of ~80 markdown patterns backing a compiled index, queried by both the builder agent and the maintenance agent before they touch code.

Scale (mid-2026)

  • ~3,900 site-specific scraper adapters across 40 countries.
  • ~132,000 live animal listings, refreshed continuously.
  • ~3,900 shelters in the canonical registry.
  • 700,000+ photos in R2, lifecycle-managed.
  • ~2,700 long-form articles (country guides, breed pages, health-certificate walkthroughs).
  • 16-locale UI across web and native mobile.

Why it's an integration story

The hard problems aren't the user-facing app — they're the compounding automated maintenance of a long-tail data pipeline. Three agents form a self-improving loop:

  • scraper-wave (builder): orchestrates parallel sub-agent builds of new adapters. Pre-flight hands each sub-agent a briefing of matching recipe patterns (Wix DOM paths, WP REST quirks, Drupal/Joomla/Squarespace conventions, per-language adopted-filter regexes, photo-source priority orderings).
  • scraper-triage (maintainer): a daily cron diffs broken adapters against last week, classifies the breakage via an 8-cause decision tree (FALSE_BROKEN / DEAD / WAF_NEW / URL_CHANGED / JS_NEW / PHOTO_CDN_MIGRATED / SELECTOR_DRIFT / AGGREGATOR_CHANGED), and walks per-cause recipes. Hard rules baked in from real incidents — e.g. "fail-closed when the adopted-leak filter has no data" (prevents 1,000+ ghost-listing leaks) and "no synthetic descriptions, ever."
  • recipe-distiller (compounder): every wave merge runs a distillation agent that proposes new recipes from net-new patterns, so the next wave consumes what the last one learned.

Other automated discipline:

  • A mobile release pipeline that gates a 7-minute build behind a 5-second sanity check — certs, profiles, keystores, fingerprints, EAS env, JSON validity, toolchain present — so failures surface up front.
  • A photo-lifecycle pipeline rolled out behind the same off → observe → dry-run → enforce flag, with a 30-day post-rollout review before any destructive operation goes live.
  • An MV-refresh + nightly-ANALYZE cron pair that recovered the site from a 504 outage in ~200 ms after Postgres stats drifted under heavy scrape ingest.

The same integration-and-reliability patterns show up in the marketing-data systems I build at Bagjump and in the AI-agent platform I run for a consulting client.

← All work