Japan Itinerary Maker
Pick places, set your dates, and get a route-aware day-by-day plan with a map.

- Role
- Solo developer
- Period
- Jul – Oct 2026
- Stack
- Next.js 16, TypeScript, Leaflet, PostgreSQL, Nominatim, Vitest, React 19
The problem
Planning a trip across Japan means choosing places in several regions, then finding an order that does not double back and a daily load that fits. Most planners leave the routing to you. I wanted to pick places and dates and get a sensible plan to adjust, not a blank calendar.
What I built
- A curated catalog of 152 places across 14 regions, with categories, visit durations and suggested activities.
- Itinerary generation that orders regions along the travel corridor and packs places into days by their visit length.
- Fixed-date pins, so a timed booking stays on its day while the free places are arranged around it.
- A Leaflet map with day-numbered markers, and custom places added by search or a drag-and-drop pin.
- Share links: trips stored in PostgreSQL under short unguessable codes, with a compact URL-safe encoding as a fallback.
- A reservation and ticket assistant that lists what to book ahead, and accommodation suggestions based on the plan.
- Geocoding through OpenStreetMap Nominatim behind a thin route handler, with the parsing kept pure and unit-tested.
How it works
- 01
Choose places
Browse the catalog by region and category, or add a custom place.
- 02
Set dates
A date range, with optional fixed dates for timed visits.
- 03
Order regions
Regions follow the travel corridor from the start city to the end city.
- 04
Pack days
Places fill days by visit length, about nine hours of sightseeing each.
- 05
Map and plan
Day-numbered markers, a timed day plan and a list of what to book.
- 06
Share
The trip's inputs are stored under a short code and rebuilt on open.
Decisions
Store the inputs, not the rendered plan
A shared trip stores place ids, dates and options, and the page rebuilds the itinerary with the same functions the planner uses. Storing the finished schedule was simpler to render, but every shared link would freeze an older version of the routing.
Route along a corridor
Regions follow a fixed north-to-south travel corridor, and the planner sweeps along it to avoid backtracking. A general shortest-route search over every place was the alternative; the corridor gives predictable plans that are easy to test.
Codes a person can read aloud
Share codes are eight characters from an alphabet without I, L, O, 0 or 1, so they survive being read off a screen or said aloud. Long random IDs were the alternative; they are awkward to share by voice, and eight characters already allow about 850 billion codes.
Evidence
- 33 commits, all mine
git shortlog -sn HEAD- 152 places in the catalog
grep -c '{ id: "' src/data/places.ts- 11 test files
git ls-files "*.test.ts"- Running app
- Live site (opens in a new tab)
- Source code
- Repository (opens in a new tab)
Next
- Weigh each leg by train travel time rather than distance.
- Add a scheduled job that prunes old shared trips, which the table is already indexed for.