040 —Business
Onderhoud van legacy sites: de val van de kleine fix
De derde 'even een kleine fix' op een WordPress uit 2017 is waar freelancers failliet gaan. Een retainer die risico beprijst, geen toetsaanslagen.
Een dinsdagmiddag in mei. Een klant mailt: 'Kun je even het telefoonnummer in de footer vervangen? Kost je een seconde.' Je opent de site via SFTP. Het nummer staat hardcoded in header.php van een child theme uit 2017. Dat child theme overschrijft een parent theme dat ooit gefork is van Twenty Sixteen en nooit meer is bijgewerkt. Het nummer staat ook in drie widget areas (geserialiseerd in wp_options), in de customizer (alleen op de homepage), en in een handgeschreven shortcode die iemand in 2019 op een contactpagina heeft gezet. Twee uur later heb je de fix op vijf plekken doorgevoerd. De zesde plek komt de volgende ochtend bovendrijven op de checkoutpagina, als de klant belt om te vragen waarom de footer in checkout nu leeg is. Tarieven voor legacy-onderhoud staan of vallen bij dit soort tickets.
Dit is de derde 'even snel een kleine fix' van dit kwartaal. Je hebt 0,25 uur gefactureerd omdat je zei dat het een seconde zou kosten, en de rest van de week vraag je je af waarom je uurtarief eruitziet als dat van een barista. Welkom bij onderhoudstarieven voor legacy-werk.
De 'kleine fix' is het duurste ticket in werk aan legacy sites. Het is ook het meest onderbeprijsd. Het eerlijke antwoord: je beprijst niet het werk zelf, je beprijst het oppervlak van de site dat het werk kan raken, en een WordPress-installatie van vijftien jaar oud heeft een oppervlak dat geen enkele junior-inschatting kan bevatten.
De economie van 'even snel een kleine fix'
Een kleine fix op een greenfield Next.js-app is ook echt klein. De footer is een component, die component wordt op één plek geïmporteerd, de imports zijn getypeerd, de wijziging is één PR, één preview-build, één merge. Twintig minuten van verzoek tot deploy.
Een kleine fix op een WordPress-site uit 2017 heeft een andere vorm. De wijziging is misschien één regel. Die regel vinden is het werk. Bevestigen dat de wijziging niets stuk maakt wat niemand heeft gedocumenteerd, dat is het grotere werk. De deploy gaat via SFTP omdat de stagingomgeving op dezelfde shared host staat en dezelfde database deelt, dus 'deploy' betekent 'live bewerken en duimen'.
Je verkoopt geen minuten toetsenbordtijd. Je verkoopt de garantie dat de wijziging de checkout niet sloopt. Die garantie heeft een reële kostprijs, en de klant kan die van buitenaf niet zien. Hij ziet 'een telefoonnummer aanpassen' en beprijst het navenant. Jij ziet vier jaar aan add_filter-aanroepen die in de header hangen op prioriteit 11, 14 en 99, en je weet dat je alle drie moet lezen voor je het bestand aanraakt. Het WordPress hooks-systeem is mild bij binnenkomst en genadeloos bij vertrek.
Het prijsprobleem is een vertaalprobleem. Zolang de klant niet ziet wat jij ziet, is elke kleine fix een gevecht.
Oppervlak beprijzen, geen tickets
Het uurtariefmodel breekt op legacy-onderhoud omdat het de klant vraagt jouw archeologie te financieren. Elk uur dat je doorbrengt met het lezen van oude code is een uur dat je op een factuur moet verdedigen. Klanten die alleen ooit greenfield-werk hebben gekocht, snappen niet waarom 'een telefoonnummer aanpassen' negentig minuten kostte. Ze zijn niet onterecht om dat te vragen. Ze zijn onterecht in wat ze denken te hebben gekocht.
Het alternatief: beprijs het oppervlak één keer, vooraf, in een retainer die een vast aantal tickets per maand bundelt tegen een afgesproken scope. De retainer dekt:
- Een afgebakende site (één WordPress-installatie, één database, één set plugins op een bekende, bevroren versie).
- Een afgebakende ticketgrootte (elk los verzoek dat geen nieuwe plugin, database-migratie of designwerk vereist).
- Een afgebakend reactievenster (de volgende werkdag voor niet-urgent, vier uur voor site-down).
- Een afgebakende inventarisatiefrequentie (elk kwartaal besteed je een halve dag aan het opnieuw in kaart brengen van het oppervlak, want plugins schuiven).
Zodra dat staat, verdwijnt het gesprek over kleine fixes. De klant koopt de garantie, niet de toetsaanslagen. De derde 'even snel een kleine fix' van de maand komt uit een bucket waar de klant al voor heeft betaald, en bij de vierde stuur je een rustig bericht: 'we zitten aan onze bucketlimiet, het volgende ticket valt buiten scope, dit is het tarief.' Niemand wordt verrast.
Een viertier-model dat standhoudt
Vier tiers, in gewone getallen. Pas aan voor je eigen markt. Dit is wat een one-person studio of een agency van drie tot vijf mensen in Nederland realistisch kan vragen op een WordPress-, Drupal 7- of Magento 1-site van zes tot tien jaar oud, zonder dat iemand begint te lachen.
Watch (€120 / maand). Uptime-monitoring, wekelijkse back-ups, security-updates op core en plugins, een maandelijks patchrapport. Geen tickets. Bestaansverzekering. Verkoopt aan klanten die een site hebben die geld oplevert, maar er zelf niets aan veranderen.
Hold (€350 / maand). Alles uit Watch, plus twee tickets per maand van maximaal één uur. Verkoopt aan klanten die een paar keer per jaar de tekst aanpassen.
Maintain (€750 / maand). Alles uit Hold, plus zes tickets per maand van maximaal twee uur, plus een kwartaal-audit op het oppervlak. Verkoopt aan klanten die de site als werkend bedrijfsmiddel behandelen.
Custodian (€1.800 / maand). Alles uit Maintain, plus een vast maandelijks belmoment, eerste-lijns-routing bij site-down-events en een jaarlijkse risico-review op de stack. Verkoopt aan klanten wiens business afhangt van een werkende site.
Tickets die boven de tijdslimiet per ticket uitkomen, gaan over op uurtarief volgens het gepubliceerde tarief van de klant (€95 tot €140, afhankelijk van tier). De limiet is de hefboom. Telkens als een kleine fix de twee-uur-lijn passeert, begint de meter te lopen, en de klant is daar op dag één schriftelijk mee akkoord gegaan.
De contractclausules die je daadwerkelijk redden
Drie clausules doen het grootste deel van het werk in een legacy-onderhoudscontract.
De frozen-stack-clausule. De retainer dekt de site zoals die is op de dag van ondertekening. Nieuwe plugins, theme-swaps of alles wat de PHP-versie raakt, worden apart gescoped. Zonder deze clausule voegt de klant in maand twee een page builder toe en verwacht hij dat je de daaropvolgende twee weken stabilisatie absorbeert als 'onderhoud'.
De deprecation-clausule. Je bent niet verantwoordelijk voor de kosten van het draaien van niet-ondersteunde software. Staat de site op PHP 7.4 (al lang voorbij end-of-life) en dwingt de hostingmaatschappij een upgrade af, dan is de migratie een project, geen ticket. De retainer loopt door, de upgrade wordt los gefactureerd.
De single-source-of-truth-clausule. Tickets komen binnen via één kanaal. E-mail, helpdesk of Slack. Niet drie. Tickets die buiten het kanaal binnenkomen krijgen een vriendelijke verwijzing, en de klok start pas zodra het ticket in het kanaal staat. Dit klinkt kleinzielig. Het redt de relatie, want het alternatief is zes maanden aan vergeten WhatsApp-vragen die uitmonden in 'maar ik heb je dat in maart toch gevraagd?'
Optionele vierde clausule: een 'kleine fix'-pool van 30 minuten per maand, zonder limiet per ticket, voor de oprechte eenregelige aanpassingen. Brand hem op aan telefoonnummers in footers. Op is op. Die pool is het overdrukventiel dat de relatie warm houdt zonder dat de retainer leegloopt.
Hoe we het uiteindelijk hebben opgelost
Toen we Pier bouwden, liepen we hier precies tegenaan. De reden dat een kleine fix twee uur kost is bijna altijd dezelfde: je kunt de wijziging niet zien voordat je hem maakt, en je kunt hem achteraf niet schoon terugdraaien. Daarom dockt Pier tegelijk met de FTP-server en de MySQL-editor, belandt elke wijziging in de version history, en is undo één klik. Dat verandert je prijsmodel niet, maar het verandert wel wat een ticket je daadwerkelijk kost in echte wandklokminuten, en dat is het enige getal dat bepaalt of de retainer winstgevend is.
Het kleinste wat je vandaag kunt doen: open de laatste drie 'kleine fix'-tickets die je in de afgelopen 60 dagen hebt gefactureerd, tel de werkelijke tijd op inclusief het testen en de follow-up-mail, en deel door wat je hebt gefactureerd. Komt het impliciete uurtarief onder je officiële tarief uit, dan run je geen legacy-onderhoudsbedrijf. Dan run je een liefdadigheidsinstelling voor legacy-code.
— Vragen —
Wat als een klant geen retainer wil en alleen per ticket wil werken?
Quoot per ticket op 2x je impliciete uurtarief uit de retainer, met een minimum van één uur en een schriftelijke scope per ticket. De meeste klanten stappen tegen de derde factuur alsnog over op de retainer.
Hoe vaak moet de audit op het oppervlak gebeuren?
Per kwartaal voor actieve sites, twee keer per jaar voor statische. Audit betekent: opnieuw de geïnstalleerde plugins, PHP-versie, theme-overrides en eventuele cronjobs op een rij zetten. Een halve dag, gefactureerd binnen de retainer.
Werkt dit model ook voor Magento 1 en Drupal 7?
Ja, en de tiers moeten hoger geprijsd zijn omdat de arbeidspool kleiner is en het security-oppervlak groter. Voeg een deprecation-clausule toe die de EOL-datum expliciet noemt.