Een fotograaf die ervan leeft, betaalt voor drie abonnementen.
Eén voor de portfoliosite, want de klant moet het werk zien. Eén voor klantgalerijen, want 400 foto's via WeTransfer sturen is geen oplevering. Eén voor de facturatie, want het geld moet binnenkomen.
Geen van de drie weet van de andere twee. Dezelfde klant staat er drie keer in, onder drie net iets andere spellingen. De factuur weet niet bij welke galerij hij hoort. En de boeking waar het allemaal mee begon staat ergens anders, meestal in de agenda op een telefoon.
profotograaf is die drie dingen op één plek, op het eigen domein van de fotograaf. Een platform dat ik actief uitbouw, geen weekendproject dat ik in de kast heb gezet.
Waar de waarde zit
Niet in "minder tools". Dat roept iedereen, en daarna kopen ze er weer een bij.
De waarde zit in de koppelingen. Een boeking die de factuur maakt. Een galerij die bij een klant hoort in plaats van bij een link. Een portfolio en een klantomgeving op hetzelfde domein, zodat wie op de een binnenkomt, de ander ook vindt. Zolang die drie in de databases van drie verschillende bedrijven staan, kan dat niet.
Dat het bij fotografen begint, is een bewuste keuze. Technisch genoeg om een echte site te willen, niet van plan enterprise-tarieven te betalen, en net ijdel genoeg — in de goede zin — om zich druk te maken over hoe de galerij eruitziet als een klant hem op Instagram zet. Verwante beroepen gebruiken bijna dezelfde functionaliteit, maar wie te vroeg generaliseert, bouwt iets voor niemand.
Pagina's zijn blokken
Het hart van het product is een blokgebaseerde paginabouwer. De publieke site, de over-pagina, de contactpagina en de deelpagina's van galerijen komen allemaal uit dezelfde renderer.
Blokken zijn hier belangrijk om een weinig spannende reden: een fotograaf wil dat de site van hém is, en dat lukt niet met een sjabloon waarin je drie kleuren mag kiezen. Er zijn negen themafamilies — atelier, editorial, frame, grid, lumen, magazine, minimal, mono, studio — en met de paginabouwer haal je er eentje uit elkaar.
Alles aan zo'n pagina is geversioneerd. Concept en gepubliceerd zijn aparte statussen, dus je site verandert niet terwijl jij halverwege een bewerking bent. Door de paginaversies is terugdraaien een kwestie van klikken, geen supportticket. En tekst die de AI voorstelt komt binnen als voorstel dat je aanneemt of weggooit, nooit als een wijziging die stilletjes op je live site is doorgevoerd. De AI schrijft een concept, de fotograaf beslist.
Galerijen zijn de motor
De galerij is veruit het grootste deel van de codebase, en dat is geen toeval. Het is het deel dat klanten zien.
Samengestelde en mapgebaseerde galerijen, presigned uploads met asynchrone variantverwerking, een fotobibliotheek met mappen, watermerken, deelpagina's met wachtwoord en vervaldatum, proofingrondes waarin een klant selecties markeert en opmerkingen achterlaat, downloads, cijfers per galerij.
De publieke pagina's zijn server-gerenderde Go-templates en geen single-page app. Dat is geen technische voorkeur maar een keuze over distributie. Een galerijlink belandt in WhatsApp, in Slack, in een Instagram-story. Hij moet zijn eigen previewkaart laten zien zonder op JavaScript te wachten, en hij moet vindbaar zijn. Wat gedeeld wordt laat het product groeien, dus krijgt precies dat deel de server-rendering.
De app raakt de foto's nooit aan
Foto's gaan rechtstreeks van de browser naar object storage, via een presigned PUT. De applicatie zet er een handtekening onder en gaat aan de kant.
Dit zou ik iedereen aanraden die zoiets bouwt. Een fotograaf zet in één keer een bruiloft van 4 GB online. Laat je die bytes door je applicatie lopen, dan run je feitelijk een bestandsdienst met toevallig een web-UI eraan, en vecht elke upload met elke pagina om hetzelfde geheugen. Geef in plaats daarvan een handtekening af, laat de browser rechtstreeks met de bucket praten, en maak de varianten achteraf.
Eén binary, drie modi — maar daar gaat het niet om
Voor de volledigheid, want over dit patroon heb ik eerder geschreven: het is één Go-binary die op een modusvariabele schakelt tussen een writer die de migraties en alle schrijfacties bezit, een aantal alleen-lezen readers, en een controller die eigen domeinen regelt. Postgres in elke uitgerolde omgeving, lokaal SQLite. Verticale feature-slices onder internal/features/, zodat een wijziging aan galerijen in de galerijmap blijft.
Zo is het gebouwd, en dat is bewust: één man, geen ops-team, dus elke keuze valt uit naar zo min mogelijk bewegende delen. Maar ik pas ervoor op om de techniek eronder als het product te verkopen. Die ene-binary-truc is het aardige aan BugBarn en FunnelBarn, want daar is "dit draai je zelf op een kleine VPS" de hele pitch.
Bij profotograaf ligt dat anders. Niemand host zijn eigen fotografiebedrijf. De klant ziet die binary nooit. Die ziet of de galerij laadt op een telefoon, in een trouwlocatie, met twee streepjes bereik.
Wat er nog komt
Eigen domeinen zijn live, dus de site van een fotograaf staat op zijn eigen adres en niet op een subdomein van mij. Facturatie zit erin. De klantomgeving — overzicht, galerijen, proofing, facturen, berichten — is server-gerenderd op /{slug}/client/, met ondertekende cookies in plaats van een sessietabel.
Wat volgt is meer van de zakelijke kant, en vooral meer koppelingen: de boeking die van de vragenlijst weet, de vragenlijst die het contract wordt, het contract dat de factuur wordt. Daar draait het om, en het is precies wat drie losse abonnementen je nooit gaan geven.