Work  /  Zarya

Zarya logo

"Is this trip possible for me?"

Not the price of a flight to Tokyo. With this passport, this residence status, this budget and these dates: can I get in, and on what conditions?

Type
Multilingual web platform
Languages
French, English, Spanish, Portuguese, Arabic
My role
Solo, design and development

The question nobody handles seriously

Travel comparison sites all answer the same question: how much does it cost. None of them answers the one that comes first: can I, with my travel document and my situation, make this trip at all.

Zarya offers four analysis paths depending on intent — tourism, business, study, relocation — and each one asks its own questions. A student stay is judged on tuition and proof of funds, not on the price of activities.

The user names a destination, or asks for suggestions based on their profile. They get a complete guide: feasibility verdict, visa rule for each leg with its dated official source, documents to prepare, itemised budget, day-by-day itinerary, timeline for the paperwork, map of the places and filtered booking links.

Alongside that, tools that answer instantly without going through any AI model: profiles for 250 countries, a passport comparator, a currency converter, clocks for every time zone in force, a travel journal.

The constraint that shaped everything

The intended audience includes restricted-mobility passports, diaspora communities, students and people in complicated administrative situations. For them, wrong visa information does not cost an inconvenience: it costs denied boarding, or an asylum claim deemed abandoned.

Hence the rule that governs the whole architecture:

Wrong data does not show. It displays in green, it looks certain, and nothing crashes. Everything else is built against that.

What sets the product apart

  • The return journey is verified. Nobody does this. If you live somewhere other than your country of nationality, your readmission depends on your residence permit, not on your passport: a permit that expires during the trip is enough to leave you locked out.
  • The travel document is taken for what it is. A refugee or stateless travel document does not open the same doors as an ordinary passport from the same country.
  • Every rule carries its source, dated. Anything that could not be confirmed is marked "not verifiable" rather than presented as certain.
  • The confidence score is computed, not self-declared. It was not at the start: the model was asked to judge the quality of its own sources, and it answered "medium" almost every time. It now rests on countable facts — rule confirmed at each leg, government source, source less than eighteen months old, precise dates, flight price actually retrieved — and the screen shows what is missing and how to fill the gap.
  • The activities budget is derived from the itinerary, not from a hunch: the sum of the prices of the places actually on the schedule, plus a margin. And the calculation refuses to conclude when too many prices are missing, rather than returning a total that is too low and looks precise.
  • The booking links filter on the chosen budget range and surface local operators that no international comparison site lists.

The stack

Next.js 16 (App Router, Turbopack), React 19, Tailwind CSS. Supabase for authentication, PostgreSQL and row-level security. A language model for the synthesis, a sourced web search, and validation of real addresses by geocoding. Deployed on Vercel: static generation, API routes and scheduled jobs.

JavaScript rather than TypeScript — a deliberate choice, offset by purpose-written automated checks. Six production dependencies only.

~30,000lines of JS/JSX
74components
50domain modules
20API routes
1,449static pages

The part I want to highlight: verification

Without TypeScript, and with a language model in the loop, the central question becomes: how do you know it works?

The repository holds 22 purpose-written check scripts, run by a single command and in continuous integration. Each one was born of a real failure and carries, in a comment, the story of that failure.

  • check-countries verifies that all 250 selectable countries resolve. It exists because 38 of them did not: the picker library wrote "Democratic Republic of the Congo", the database knew "Congo - Kinshasa". Nationality was stored empty, the screen kept showing the chosen country, and the field only came back blank on the next reload.
  • check-await looks for any asynchronous call that is neither awaited nor chained. Written after a forgotten await took down the whole signed-in area: reading a property on a promise gives undefined, which is perfectly legal and perfectly silent.
  • check-promises refuses to let a form show success without having sent anything. Two forms were writing to browser storage and then thanking the user.
  • check-currencies caught Croatia shown in kuna three years after it switched to the euro.
  • check-imports walks the import graph of the application's 175 entry points in a few seconds, where a full build would take several minutes.

On top of those: consistency across the five dictionaries, search indexing, deadlines, prompts, visa reliability, time zones and the destination fit score.

The important part: the checks are themselves tested. For each one, I reintroduced the failure it watches for, to confirm that it fails. A check you have never seen fail proves nothing.

Five languages, with the URLs to match

The interface exists in five languages, including Arabic with right-to-left layout. But the site was translated for humans and monolingual for search engines: the language lived in a React context, chosen after load. A crawler only reads the first HTML served, always in French. Someone searching for "Canada visa" in Spanish could not find the site.

The rework gives each language its own URLs (/faq, /es/faq, /ar/faq), with per-language canonicals, reciprocal hreflang and x-default. French keeps its bare URLs so no existing link breaks. The variants duplicate no code: they are re-exports of the French route, and a check verifies that none of them drifts from its source.

The Next 14 to 16 migration

Run in two deliberate stages. First the move of the request APIs to asynchronous under the old version: awaiting an ordinary value returns it unchanged, so the rewrite was verifiable on its own. Only then the version bump. If something broke afterwards, it could not be the rewrite.

4 → 0high security alerts
18 sfull build
2 stageseach one verifiable alone

One breaking change appeared in no documentation and only surfaced on a real build: Next 16 rejects a re-exported route segment config, because it reads it by static analysis.

What it demonstrates

  • A product that takes seriously the users everyone else ignores: passport, residence status and the return journey treated as first-order variables.
  • Engineering discipline: reproduce before fixing, verify against the output rather than the code, test the checks themselves.
  • Interface honesty held as a rule: never display a promise the code does not keep. When a feature is not wired up, the interface says so instead of pretending.
  • The ability to carry out a heavy migration on a real codebase, sequenced so that each stage stays verifiable in isolation.

A first meeting costs nothing.

Thirty minutes, no obligation, at your place or by video call. Reply within 24 hours, seven days a week.

Book a meeting