← Blog

Mobile apps

Field-service app herbouwen: Cordova naar Expo + Supabase

Een klimaattechniek bedrijf in Apeldoorn liet 38 monteurs werken op een 9 jaar oude Cordova-app, dichtgeplakt met 41 plugin-patches. Wij bouwden hem opnieuw op Expo en Supabase.

Jacob Molkenboer· Oprichter · A Brand New Company· 22 jun 2026· 8 min
Leren notitieboek met groen lint, koperen schietlood op indexkaart, groene lakzegel, potloodstompje op ivoor papier.

Om 06:42 op een dinsdag in februari ligt een monteur in een kruipruimte in Apeldoorn-Zuid bij een Vaillant ecoTEC die de druk niet vasthoudt. Geen bereik. De oude app, geschreven in Cordova en jQuery Mobile en gesigned door een developer die in 2019 vertrok, heeft net besloten dat het F-gassen-formulier opnieuw van de server moet laden.

Dat kan niet. Hij fotografeert de manometer met zijn eigen telefoon, schrijft de waarde op een papiertje en rijdt door naar het volgende adres. Het kantoor typt het vanavond wel in.

Dit is het moment waarvoor we zijn ingehuurd. Het bedrijf draait 38 monteurs vanuit één locatie, doet ruwweg 1.840 storingsbezoeken per week op de Veluwe, en stond op het punt een Play Store-deadline op target SDK 34 te missen met een app die nog altijd een WebView-shim uit 2016 inbouwde.

De eenenveertig patches

De originele app ging in 2017 live. Toen wij hem auditten, bevatte de repo 41 Cordova-plugin-patches, waarvan er 19 wezen naar GitHub-forks van accounts die sinds 2020 niets meer hadden gepusht. Twee van die forks waren soft-deleted. De buildserver was een Mac mini onder een bureau bij IT, en alleen de office manager kende het keychain-wachtwoord.

De monteurs vonden de app prima, in de zin dat ze een woordenboek aan workarounds hadden opgebouwd. "Gewoon afsluiten en opnieuw starten" was de gedocumenteerde stap drie van elke troubleshooting-flow. Het F-gassen-logboek crashte op iOS zodra je een vierde foto wilde toevoegen. Servicerapporten synchroniseerden wanneer ze daar zin in hadden. Twee keer per jaar moest de hele database opnieuw worden opgebouwd vanuit een CSV-export, omdat IndexedDB op oudere Android-toestellen door zijn quota heen ging.

Niets daarvan was op een gegeven dag een crisis. Het was een belasting van zo'n tien minuten per monteur per dag, elke dag, oftewel ruwweg 100 werkuren per week die het bedrijf weggaf aan een webstack uit 2017.

Expo, Supabase en een bewust saaie stack

We kozen Expo en Supabase voor de herbouw, en we willen specifiek zijn waarom, want die keuze wordt makkelijk gelezen als mode.

Expo gaf ons EAS Build (geen Mac mini), config plugins (geen handmatige native edits) en over-the-air updates die de office manager kan uitrollen zonder dat de Play Store er drie dagen over doet. Supabase gaf ons Postgres, row-level security, edge functions en storage in één product, gefactureerd op één plek, met één set credentials om te roteren. Het team dat dit moet draaiende houden nadat wij weg zijn: twee parttime IT'ers en één externe developer. Hoe minder accounts, hoe beter.

We kozen niet voor React Native CLI. We kozen niet voor Firebase. We kozen niet voor Flutter. Beide opties lagen op tafel; de doorslag was operationeel, niet esthetisch. EAS Update laat ons binnen een uur een hotfix uitrollen naar alle 38 telefoons als een monteur om 17:30 belt dat er iets mis is. Niets anders dat we testten haalde die loop.

Het principe daaronder: kies de stack die het in-house team zelf in de lucht kan houden. De beste architectuur is degene die je over achttien maanden niet terug nodig heeft.

Offline-first NEN 1010-keuringen

Het moeilijke was niet de UI. Het moeilijke was dat een NEN 1010-keuring een formulier met 40 velden is dat invulbaar moet zijn in een kruipruimte zonder bereik, gefotografeerd, op het toestel ondertekend door beide partijen, en op kantoor moet aankomen vóór de monteur, het liefst zonder conflict.

We gebruikten Expo SQLite als source of truth op het toestel, met een sync-laag die mutations via Supabase naar een Postgres-tabel pusht. Het schema is voor inspectie-records bewust append-only op rij-niveau: een monteur kan een ingediende keuring niet bewerken, alleen een addendum indienen. Dat loste het conflictprobleem op door het weg te halen.

create table inspections (
  id uuid primary key,
  job_id uuid not null references jobs(id),
  monteur_id uuid not null references users(id),
  payload jsonb not null,
  signed_at timestamptz not null,
  client_signature_svg text not null,
  monteur_signature_svg text not null,
  device_id text not null,
  created_at timestamptz not null default now()
);

alter table inspections enable row level security;

create policy "monteur reads own"
  on inspections for select
  using (monteur_id = auth.uid());

create policy "monteur inserts own"
  on inspections for insert
  with check (monteur_id = auth.uid()
    and signed_at <= now() + interval '5 minutes');

De vijf minuten klok-skew-tolerantie op signed_at zit erin omdat monteur-telefoons niet NTP-gesynct zijn als ze ondergronds zitten. Dat hebben we de harde manier geleerd in de eerste betaweek.

Foto's zijn een apart probleem. Elke keuring bevat tot twaalf foto's, en de verbinding terug bij de bus is vaak één streepje 4G in een metalen kooi. We zetten uploads in een wachtrij met exponential backoff, dedupliceren op SHA-256, en laten het formulier indienen voordat de foto's klaar zijn. De keuring is juridisch compleet op het moment dat de handtekening landt; de foto's komen onderweg naar huis bij.

Een diagnose-coach aan een korte lijn

De feature die intern het meest werd gevolgd, was de AI-diagnose-coach. De monteurs wilden hem. De ondernemingsraad was nerveus. We werden het eens over een smalle scope en hielden vast.

De coach krijgt drie inputs: het type apparaat en model, de foutcode (als die er is), en de vrije-tekstbeschrijving van wat de monteur ziet. Hij geeft een ranglijst van waarschijnlijke oorzaken en de volgende diagnostische stap terug. Hij bestelt geen onderdelen. Hij sluit geen jobs. Hij praat niet met de klant. Hij schrijft een suggestie in een zijpaneel; die leest de monteur, negeert hij, of volgt hij op. Elke interactie wordt gelogd.

We gebruiken Claude Sonnet 4.5 via de Anthropic API, met een system prompt die het model vastpint op Nederlands, beperkt tot HVAC-diagnostiek en een JSON-respons afdwingt. De terugkerende les uit het publieke gesprek over betrouwbare agentic AI-systemen is dezelfde als die wij intern trokken: de truc is het model minder autoriteit te geven, niet meer. Onze coach is geen agent. Het is een zoekmachine die iets weet over ketels.

// edge function: diagnose-coach
const system = `Je bent een HVAC diagnose-assistent voor monteurs in Nederland.
Antwoord altijd in het Nederlands en uitsluitend in geldig JSON volgens dit schema:
{ "oorzaken": [{ "oorzaak": string, "kans": number, "volgende_stap": string }] }
Geef maximaal 5 oorzaken, gesorteerd op kans (0-1).
Verwijs niet naar onderdelen tenzij de monteur die expliciet noemt.`;

const res = await anthropic.messages.create({
  model: "claude-sonnet-4-5",
  max_tokens: 700,
  system,
  messages: [{ role: "user", content: prompt }],
});

Eén planningsnotitie, omdat het onze rollout raakte: Anthropic kondigde aan dat bepaalde API-mogelijkheden vanaf 8 juli 2025 organisatie-ID-verificatie zouden vereisen. We moesten de verificatie twee weken voor de cutover inplannen. Lever je een Claude-feature uit in een gereguleerde sector op een deadline? Zet die stap nu in het plan, niet de dag ervoor.

Waarschuwing

Heeft de coach het mis, dan is de monteur aansprakelijk. We hebben de disclaimer zichtbaar in het zijpaneel gezet en elk suggestie-en-actie-paar gelogd voor de OR-audit. Bouw de bewijsstukken vóór je de feature bouwt.

F-gassen-logboek naar het RVO-register

Het saaie deel kostte drie weken. EU-verordening 517/2014 over gefluoreerde broeikasgassen verplicht operators van koelsystemen boven bepaalde drempelwaarden om een logboek bij te houden van lekcontroles, bijvullingen en terugwinning. In Nederland wordt dat gerapporteerd aan het RVO-F-gassenregister.

Het register heeft een upload-formaat, geen realtime API. We bouwden een logboek per monteur in Postgres, met hetzelfde append-only patroon als de keuringen, en een nightly edge function die de RVO-compatibele export produceert en naar de boekhouder mailt voor review en indiening. De boekhouder tekent het af; het systeem dient nooit zelf in bij een overheids-endpoint zonder mens in de loop. Dat was een bewuste keuze, gemaakt om aansprakelijkheid, niet om techniek.

De migratie van het oude logboek was het ergste. Het oude systeem sloeg F-gas-metingen op als één TEXT-kolom met puntkomma's. We schreven een one-shot importer die ruwweg 14.000 historische entries parste, 73 markeerde die de regex niet aankon, en die aan de boekhouder gaf. Zij ving er één uit 2019 op waar een komma als decimaal was gebruikt en de oude app stilletjes 0,6 kg R-32 van het dossier van een school had gedropt. Die ene fix rechtvaardigde de migratie op zichzelf al.

Wat live ging, en wat het de dagelijkse praktijk scheelde

De nieuwe app ging in maart 2026 live na een interne beta van zes weken met acht monteurs. Drie maanden later:

  • De gemiddelde tijd voor een keuring zakte van 18 naar 11 minuten.
  • De workflow 'kantoor typt het vanavond in' is weg. De dagafsluiting voor het planningsteam ging van 90 minuten naar onder de 15.
  • App-gerelateerde supportcalls vanuit het veld zakten met zo'n 70%. Wat overblijft, zijn vrijwel uitsluitend wachtwoordresets en kapotte schermen.
  • De diagnose-coach wordt gebruikt bij ruwweg 40% van de storingsbezoeken. De meest ervaren monteurs gebruiken hem het minst, zoals we verwachtten. Twee senior monteurs vertelden ons dat ze hem vooral inzetten om hun eigen onderbuik nog eens te checken.

Het cijfer waar wij het meest om geven, staat op geen enkel dashboard: niemand typt om 19:00 nog een manometer-waarde van een papiertje over op een desktop.

Wat we anders zouden doen

Drie dingen, als we opnieuw zouden beginnen.

We zouden de offline conflict-UI als eerste bouwen. We behandelden hem als edge case in week twee en als feature in week zes, en moesten hem alsnog over drie schermen retrofitten. Wat je ook denkt dat je offline-rate is voor een field-service app, hij is hoger.

We zouden de AI-feature bij de launch nog smaller scopen. De diagnose-coach die we hebben uitgeleverd is ruwweg de helft van wat we initieel scopten. Snijden was de juiste keuze. We hadden eerder kunnen snijden.

We zouden het exportformaat vanaf dag één versioneren. De RVO-template veranderde één keer tijdens het project. We moesten dat over twee tabellen en een JSON-kolom najagen omdat we te vroeg hadden afgevlakt. Een version-veld was tien minuten werk geweest in week één.

Toen we dit bouwden voor het klimaattechniek bedrijf in Apeldoorn, liepen we er steeds tegenaan dat operations-regelgeving en monteurs-realiteit niet hetzelfde vocabulaire spreken. We losten het op door het datamodel vanuit de regelgeving terug te schrijven en de UI te laten vertalen. We doen dit soort mobiele-app-herbouw vaak genoeg dat de eerste slide van elke offerte een crashlog-telling is.

Heb je een Cordova-app die met patches overeind staat, een papieren spoor naar een Nederlands register, en een team dat de tool niet meer vertrouwt: open het crash-dashboard van de oude app en tel de unieke device-modellen van de afgelopen 30 dagen. Dat getal is de omvang van je probleem.

Kern

Kies de stack die je in-house team in de lucht kan houden. De beste field-service-herbouw is degene die zijn consultants over achttien maanden niet terug nodig heeft.

FAQ

Waarom Expo en niet bare React Native voor een field-service app?

Operationele redenen. EAS Build maakt de in-house Mac overbodig, config plugins maken handmatige native edits overbodig, en EAS Update levert hotfixes binnen een uur zonder Play Store-review. Een klein in-house team kan dat draaiende houden.

Hoe ga je om met sync-conflicten in een offline-first inspectie-app?

We maakten inspectie-records append-only op rij-niveau. Een ingediende keuring kun je niet bewerken, alleen aanvullen met een nieuwe addendum-rij. Het conflictprobleem verdwijnt, want twee toestellen kunnen nooit hetzelfde record overschrijven.

Is het veilig om een LLM voor een gereguleerd vak als HVAC te zetten?

Ja, als zijn autoriteit smal is. Onze coach suggereert oorzaken en volgende stappen in een zijpaneel, sluit nooit jobs af, bestelt nooit onderdelen, praat nooit met de klant. Elke suggestie wordt gelogd voor audit. De monteur blijft verantwoordelijk.

Hoe komt het F-gassen-logboek bij het RVO-register?

We exporteren elke nacht een RVO-compatibel bestand uit Postgres en mailen het naar de boekhouder, die het reviewt en indient. Geen geautomatiseerde indiening bij een overheids-endpoint. De mens in de loop is een bewuste aansprakelijkheidskeuze.

case studymobile appsmigrationai agentsoperations

Iets bouwen?

Start een project