An AI-powered travel guide generator: a visitor names a destination and their trip preferences, and WhereNext.ai produces a complete, long-form guide. The frontend is cool and all, but the work I'm most proud of is the queued, orchestrated, retry-safe pipeline that assembles a dozen independently-generated pieces of content into one coherent guide without collapsing when a single model call misbehaves. Even more impressive: I hand-wrote most of this because AI was new enough when I started that tab completions had barely come out.
WhereNext.ai
An AI travel guide generator that turns a destination request into a complete long-form guide—logistics, itinerary, imagery, and FAQs—via a queued, orchestrated pipeline that assembles a dozen parallel model calls without collapsing when one misbehaves.

WhereNext.ai
Live
Introduction

The Challenge
The idea was basic enough: generate complete, long-form AI travel guides on demand. Guides need to include several basic sections: introduction, logistics, climate, accommodation, food, attractions, day-by-day itineraries, FAQs, key facts, phrases, and imagery. The challenge? Do it quickly and make it resilient. This required coordinating a dozen parallel model calls into one coherent guide without collapsing when a single API call fails--a perfect setup for a durable function!
The Outcome
A live, hand-architected pipeline coordinating OpenAI, DALL·E, Midjourney, Cloudinary, Strapi, Brevo, HubSpot, Azure SQL, and Service Bus across ~11,500 lines of TypeScript to assemble multi-section guides with original imagery from a single form submission.
Tech Stack
Frontend
- Next.js
- React
- SCSS
- Apollo Client
- SWR
- Mantine
- Mapbox
- React Testing Library
Language
- TypeScript
- Node.js
- GraphQL
- JSON
- Zod
Backend
- Azure Functions
- Azure Service Bus
- Azure SQL
- Azure Durable Functions
- OpenAI
- Cloudinary
- Strapi
- Brevo
- HubSpot
- REST
- Azure
- Algolia
- Clerk
- Google Places
- Viator
- JSON-LD
- SQL Server
DevOps
- Zapier
- GitHub Actions
- Dokploy
- Vercel
- Datadog
- Azure Application Insights
Design
- Midjourney
- DALL·E
Tooling
- Jest
- Two separate Azure Functions apps, deliberately split. A queue manager exposes an HTTP enqueue endpoint that drops requests onto an Azure Service Bus queue and records the task in Azure SQL, with endpoints to insert, update, and read enqueued status — so admission and status tracking survive independently of whichever worker eventually runs the job.
- A Azure Durable Functions orchestration app for generation itself: a top-level
generate-guideorchestrator runs setup activities, then delegates to three sub-orchestrators that each fan out and rejoin. - TypeScript throughout (~11,500 lines in the functions app alone), on Node.js 22.
- OpenAI for all text generation using structured-output schemas, plus DALL·E and (earlier) Midjourney for imagery, with Cloudinary handling image delivery.
- Strapi as the content store, written to over GraphQL and REST — shared with the sibling product WhereNext.to — with a Next.js front end rendering the finished guides.
- Business-side integrations: Brevo for transactional email and contact lists, HubSpot for deals, line items, products and engagements, and Zapier webhooks for downstream events.
- CI/CD via GitHub Actions with OIDC-based Azure login, deploying
mainto production anddevto a staging slot. CMS infrastructure was recently migrated to Dokploy, covering both WhereNext products.

Key Implementation Details
Three fan-out/fan-in sub-orchestrators
- Guide info runs eight generations concurrently via
Task.all: introduction, travel logistics, climate and seasons, accommodation, food and drink, attractions and activities, other experiences, and partnership resolution. - Itinerary generates a skeleton first, then fans out one task per month, then persists the result to Strapi.
- Images generates and uploads in parallel, with generated URLs cached in Azure SQL keyed by image type, provider and variant.
- Every activity is invoked through
callActivityWithRetrywith a 5-second first retry and 3 attempts, so a transient model or API error retries in place rather than discarding a half-built guide.
Chunked itinerary generation with a retry boundary that matches the failure unit
- Itineraries are generated in week-day chunks rather than in one shot, so the retry boundary lines up with the unit that actually fails — a broken chunk is regenerated on its own instead of throwing away a finished itinerary.
- Chunking, schema shape and persistence are each covered by their own tests (
itinerary-chunked,itinerary-schema,week-day-itinerary-data,strapi-itinerary).
An anti-repetition ledger carried between chunks
- Piecewise generation creates a real problem: each chunk has no idea what the previous ones recommended. Each chunk is therefore handed a compact ledger of what earlier chunks already used, so a seven-day guide doesn't suggest the same market four times.
prose-length-safety.ts — one source of truth for prose length
- The limits the post-generation shortener enforces and the
maxCharactersguidance baked into the OpenAI JSON schema descriptions both read from a singlePROSE_LIMITSobject, so the two can't silently drift apart — a nasty and hard-to-spot class of bug, designed out rather than tested for. - All prose passes through
prepareProseForPersistencebefore it reaches Strapi, giving a single choke point for length safety.
An AI country fallback for contested geography
- Some upstream location lookups return no country division at all — disputed or contested territories. Rather than writing empty country data into Strapi and breaking webhooks and partnership targeting downstream, the pipeline resolves a best-guess country and backfills it during setup.
Structured-output discipline against early, unreliable models
- Built starting in late 2023, when getting dependable JSON out of a model was genuinely hard. Prompts are assembled from composable helpers (
prompt-helpers.ts,buildPromptText) and paired with explicit JSON schemas usingadditionalProperties: false,minItems/maxItemsand required fields, so malformed output is caught rather than persisted. - Prompt construction also adapts to trip mode — a long-stay guide asks materially different FAQ questions (visas, monthly rentals, eSIMs, coworking) than a week-long trip.
What I Contributed
- Architected a two-app Azure Functions pipeline: a queue manager decoupling admission from execution, and a Durable Functions orchestration with multiple fan-out/fan-in sub-orchestrators managing guide info, itinerary, and image generation in parallel.
- Designed chunked itinerary generation with retry boundaries matching the failure unit, preserving good data and regenerating only the bad. Deduplicated parallel calls via an anti-repetition ledger carried between chunks to prevent overlapping recommendations.
- Created a highly interactive frontend for discoverability and easy guide construction, with mapping functionality, search, account management, and site-wide notifications on guide status.
- Implemented an AI country fallback for contested territories, resolving a best-guess country during setup to prevent empty data from breaking downstream webhooks and partnership targeting.
Outcome
-
Retry-safe orchestration with activity-level retries (5-second first retry, 3 attempts) and failure boundaries, resulting in a non-deterministic process that is nonetheless reliable enough to run unattended.
A live, hand-architected generation pipeline that turns a single form submission into a complete multi-section travel guide with original imagery, coordinating a dozen parallel model calls across two Function apps, a Service Bus queue, a SQL task ledger and a shared Strapi CMS. The design decisions that matter are the defensive ones — retry boundaries drawn around the real failure units, a single source of truth for prose limits, and fallbacks for geography the upstream data can't describe — which together make a fundamentally non-deterministic process reliable enough to run unattended.