Wanderlist

City search · live place data · trips & sharing

A full-stack travel planner built on live place data — no static content, no manual curation. Search any city and get a curated split of headline attractions and local finds with photography layered in, then save to named trips, estimate a budget, view on a map, and share a read-only link. The Trip Quiz turns five questions about vibe, pace, and landscape into a ranked itinerary shortlist. Auth adds persistence; anonymous search works without an account.

What it does

Wanderlist main screen with city search and hero
Main search experience—entry into discovering a city.
  • Type any city. Wanderlist geocodes it, pulls live place data from Geoapify, layers in Unsplash photography, and returns a curated split of headline attractions and hidden gems — no content team, no manual curation.
  • Signed-in users get the full product: save places to named itineraries, see an estimated trip budget, view everything on a map, and share a read-only trip link with anyone.
  • Not sure where to start? The Trip Quiz asks five questions — vibe, duration, exploration style, landscape, pace — and returns a ranked list of personalized itinerary matches. Or browse the Discover page for curated trips filterable by type and interest, and copy any of them to your account in one click.
  • Anonymous users still get full search and recent history — the auth layer adds persistence, it doesn't gate the core experience.

What I built

  • Server-side API pipeline — geocoding, dual place queries (top spots + hidden gems), retry logic, timeouts, and optional Unsplash enrichment all live in a single Route Handler. The client gets clean, normalized results or a clear error — never a partial state.
  • Auth with row-level security — Supabase email/password auth with password reset flow. Every private route validates the user's JWT before touching the database; RLS policies enforce ownership at the data layer so the app is secure even if API logic has a gap.
  • Trip Quiz & personalization — a 5-question flow that matches users to itineraries by vibe, duration, exploration style, landscape, and pace. Results are ranked by match score with destination photography, duration, and budget — then immediately copyable into the user's trips.
  • Discover & curated itineraries — a pre-built library of trips, filterable by type (City, Country, Multi-Country) and interest tag (Culture, Food, Beach, and more). One click clones any itinerary into the user's account, ready to customize — no blank-slate friction.
  • Itineraries, saves, and sharing — users create named trips, save places into them, set a budget, and generate shareable links. Shared trips are served read-only using the Supabase service role — no auth required for the recipient, no data leakage.
  • Resilient UI states — every async path has a designed state: loading, success, empty, and error. The app never shows a spinner with no resolution, and search results return even if the history write fails — discovery is never blocked by a secondary operation.
  • Production hardening — rate limiting on sensitive routes, input sanitization, JWT validation before any trusted Supabase operation. The kind of security setup you'd ship to production at a company, not bolted on after the fact.
Search results showing place cards for a city
Search results combining top spots and hidden gems.
Trip Quiz first question — What's your travel vibe?
Trip Quiz — 5 questions that match you to a personalized itinerary.
Trip Quiz results showing ranked itinerary recommendations
Quiz results — ranked matches with destination photography, duration, budget, and interest tags.
Discover Itineraries page showing curated trip templates
Discover — curated itineraries filterable by type and interest. One click copies any trip to your account.
My Trips page showing all trips collapsed — Rome 2026, Spain 2027, and completed Greece 2025
Collapsed trip list — upcoming and completed trips with budget estimates at a glance.
My Trips page with Rome 2026 expanded, showing the Leaflet map and day-by-day place list
My Trips — expanded view with live map, day layout, and budget estimate.
Loading, empty, or error UI state in Wanderlist
UI states for loading, empty, and error scenarios.

Stack

  • Next.js 14 (App Router) — client components for interactive search and maps, Route Handlers for the API layer.
  • Supabase — auth, Postgres with RLS, and per-request clients scoped to the user's session for private data.
  • Geoapify — geocoding, places, and autocomplete. All calls are server-side; the key never touches the client.
  • Unsplash API — city hero and per-place photography when configured; the UI degrades gracefully to category-based fallback imagery when it's not.
  • Leaflet — dynamically imported map on the trips page (no SSR), with place pins pulled from saved itinerary data.
  • Netlify — deployed with a clean environment contract; portable to any Next.js-compatible host.

Data flow

  • Search
    Client posts a city string → server geocodes it → fires two parallel place queries (categories tuned for "top" vs "gems") → normalizes results → optionally enriches with Unsplash photos → writes to search history → returns JSON. If the history write fails, results still return. The client never sees a partial or inconsistent payload.
  • Private data
    Every authenticated request carries the user's access token. Server-side route handlers call getUser() before any database write; Supabase RLS policies enforce row ownership as a second layer. No auth = no data, at the DB level.
  • Shared trips
    Sharing generates a unique token on the itinerary. The shared view uses the Supabase service role to serve read-only data by token — no session required, no private data exposed.
Diagram or UI illustrating data flow in Wanderlist
Flow from search in the client through the places API to Supabase and back.

Design decisions

  • Editorial aesthetic — serif headlines, warm cream palette, and generous photography keep the places front and center. The UI stays calm enough for long browsing sessions without competing with the content.
  • Auth as enhancement, not a gate — anonymous users get the full search experience. Sign-in unlocks persistence. This is a deliberate product decision: make the value obvious before asking for commitment.
  • Complexity lives server-side — retries, filtering, image enrichment, and normalization all happen in Route Handlers. The client owns state and presentation. Easier to test, easier to change, and the browser payload stays clean.
  • Save flows are explicit — a modal prompts users to choose or create a trip before saving a place. No mystery heart button that saves somewhere unknown; every saved spot has a home.

Outcome

  • Wanderlist is a complete product, not a prototype — search, auth, trips, maps, sharing, quiz, and security all ship in one codebase and work together end to end. Every surface has a designed state for every async outcome: loading, success, empty, and error.
  • The security model is real: JWT validation at the route layer, RLS at the database layer, server-only API keys. Not a bolt-on — it was designed in from the start and holds up to the same scrutiny as a production system.
  • The resilience decisions are deliberate — search results survive a failed history write, the app degrades gracefully when third-party APIs don't cooperate, and complexity stays on the server so the client stays predictable. That's production thinking, not happy-path thinking.
  • Code lives here: github.com/meganleclair/wanderlist.