Skip to content
All projectsProduct2026 — active development

Custour

An AI travel planner that turns a sentence about your trip into a full itinerary — with real flights, hotels and places attached.

Role
Frontend and delivery
Organisation
Custour
Period
2026 — active development
Stack
  • Next.js
  • TypeScript
  • NextAuth v5
  • TanStack Query
  • Stripe
  • SERP API
  • n8n
  • Google Maps
  • Tailwind
  • GitHub Actions
  • pm2
  • Vitest

Short explanation

Custour turns a sentence about a trip into a complete itinerary. You describe where you want to go, when, and with whom; the AI returns a multi-day plan with flights, hotels, activities and places attached — and then you edit it, because no generated plan survives contact with an actual traveller.

  • Chat-first planning: natural language in, a full itinerary out.
  • Real inventory behind the plan — flights and hotels come from live search, not from the model's imagination.
  • Editable itineraries: add, remove and reorder everything after generation.
  • Google Maps for routing and place context across the plan.
  • Three subscription tiers, metered by an AI token allowance.

Why it exists

Planning a multi-day trip means holding flights, hotels, activities and routing in your head at once across a dozen browser tabs. The tools that exist either search one category well or generate itineraries that dissolve the moment you check whether the flights are real.

The bet is that those two halves belong together: generation is only useful if what it generates is bookable.

Tech stack, and why

Next.js and TypeScript on the client. This repository is deliberately frontend-only — it consumes a REST API it doesn't own, which meant the interesting problems were about boundaries rather than about the database.

NextAuth v5 for authentication, with Google OAuth and credentials. The session JWT is injected into axios interceptors, so every outbound call carries auth without each service module remembering to add it, and a proxy middleware guards the private routes at the edge rather than inside each page.

TanStack Query owns all server state. With an AI response that streams and a token balance that changes as you use it, cache invalidation is most of the actual work.

Stripe for subscriptions, n8n as the webhook layer in front of the AI, SERP API for flight and hotel search, and Google Maps through two libraries because the newer one doesn't cover everything the older one does yet.

Features

Chat to itinerary

The whole product in one interaction.

A session starts with destination, dates, travellers and origin, and becomes a conversation. Each chat in the sidebar is a trip plan — the history is the product, not a log of it.

Real flight and hotel search

SERP API results, not generated text that looks like results.

This is the line between a demo and a product. A generated plan that names a flight which doesn't exist is worse than no plan, because it costs the user the time to find out.

AI token metering

Free, Premium and Ultra, tracked server-side.

Generation costs money per call, so usage is an allowance rather than a flat subscription. Balances are tracked on the server and surfaced in the profile — never trusted from the client, for obvious reasons.

Maps and routing

Places in an itinerary are places on a map.

Delivery pipeline

Four GitHub Actions workflows, split by intent. Two run on pull requests into dev and beta and do nothing but build — a gate, so a branch that doesn't compile can't merge. Two run on push to those branches and deploy.

Deployment isn't push-to-Vercel. The workflow opens an SSH session to the server, pulls, clears the build directory, reinstalls, rebuilds and restarts the process under pm2. That's more moving parts than a managed platform, and it's also the reason I know what each of those steps is for.

Two environments

dev and beta, each with its own branch and its own box.

Branch-per-environment keeps the promotion path obvious: what's on beta is what was on dev, and nothing reaches either without passing its build gate first.

The part that was actually hard

Owning the frontend of an API you don't control. Every shape change upstream is a change you find out about at runtime, so the service layer is 11 separate modules with their own types rather than one shared client — when something moves, the blast radius is one file.

The other one is streaming. An AI response that arrives progressively fights with a cache that wants to know when a query is done. Getting the itinerary to feel like it's being written, while still landing in a cache entry the rest of the app can read, took more iterations than the generation itself.