Een werkende fotograaf 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 facturatie, want het geld moet binnenkomen.
Geen van de drie weet van de andere twee. Dezelfde klant bestaat drie keer, onder drie licht verschillende spellingen. De factuur heeft geen idee bij welke galerij hij hoort. De boeking waar het allemaal mee begon staat op een vierde plek, meestal de agenda-app op een telefoon.
profotograaf is die drie dingen op één plek, op het eigen domein van de fotograaf. Het is een platform dat ik actief uitbouw — geen weekendproject dat ik in de kast heb gezet.
De weddenschap
De weddenschap is niet dat fotografen in het algemeen minder tools willen. Dat zegt iedereen, en daarna kopen ze nog een tool.
De weddenschap is dat de koppelingen de waarde zijn. Een boeking die de factuur genereert. 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 landt de ander kan vinden. Niets daarvan kan zolang die drie in de databases van drie verschillende bedrijven leven.
De eerste doelgroep is fotografen, en dat is een bewuste keuze: technisch genoeg om een echte site te willen, niet bereid om enterprise-tooling te betalen, en ijdel genoeg — in de goede zin — om te geven om hoe de galerij eruitziet als een klant hem op Instagram zet. Aangrenzende beroepen hergebruiken bijna dezelfde functionaliteit, maar te vroeg generaliseren is hoe je iets bouwt voor niemand.
Pagina's zijn blokken
Het hart van het product is een blokgebaseerde paginabouwer. De publieke site van de fotograaf, de over-pagina, de contactpagina en de deelpagina's van galerijen komen allemaal uit dezelfde renderer.
Blokken zijn hier belangrijk om een oninteressante reden: een fotograaf wil dat de site van hém is, en een template met drie instelbare kleuren doet dat niet. Er zijn negen themafamilies — atelier, editorial, frame, grid, lumen, magazine, minimal, mono, studio — en de bouwer laat je er een uit elkaar halen.
Alles aan die pagina is geversioneerd. Concept en gepubliceerd zijn losse toestanden, dus de site verandert niet terwijl je halverwege een bewerking bent. Paginaversies maken terugrollen een klik in plaats van een supportticket. En door AI voorgestelde tekst komt binnen als voorstel dat je accepteert of afwijst, nooit als een bewerking die stilletjes op je live site is gebeurd — de AI schrijft een concept, de fotograaf beslist.
Galerijen zijn de groeimotor
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, analytics per galerij.
De publieke pagina's zijn server-gerenderde Go-templates in plaats van een single-page app, en dat is een distributiebeslissing, geen technische voorkeur. Een galerijlink wordt in WhatsApp geplakt, in Slack, in een Instagram-story. Hij moet zijn eigen previewkaart tonen zonder op JavaScript te wachten, en hij moet crawlbaar zijn. Deelbaarheid is de groeimotor, dus krijgt het deelbare oppervlak de server-gerenderde behandeling.
De app raakt de bytes nooit aan
Foto's gaan rechtstreeks van browser naar object storage, via een presigned PUT. De applicatie geeft de handtekening af en gaat vervolgens aan de kant.
Dit is de beslissing die ik het sterkst zou aanraden aan iedereen die iets vergelijkbaars bouwt. Een fotograaf upload een bruiloft van 4 GB in één keer. Als die bytes door je applicatie stromen, draai je nu een bestandsoverdrachtdienst waar toevallig een web-UI aan hangt, en concurreert elke upload met elke paginarender om hetzelfde geheugen. Geef in plaats daarvan een handtekening af, laat de client met de bucket praten, en verwerk de varianten er achteraf bij.
Eén binary, drie modi — maar dat is het verhaal niet
Voor de volledigheid, want ik heb eerder over dit patroon geschreven: het is één Go-binary die op een modusvariabele schakelt tussen een writer die de migraties en alle schrijfacties bezit, N alleen-lezen readers, en een controller die eigen domeinen afhandelt. Postgres in elke uitgerolde omgeving, SQLite lokaal. Verticale feature-slices onder internal/features/, zodat een wijziging aan galerijen de galerijmap raakt en daar stopt.
Dat is de architectuur, en die is bewust zo — één operator, geen ops-team, dus elke keuze neigt naar minder bewegende delen om te draaien. Maar ik wil oppassen dat ik het leidingwerk niet als het product verkoop. De één-binary-truc is het interessante deel van BugBarn en FunnelBarn, self-hosted tools waar "dit kun je zelf op een kleine VPS draaien" de hele pitch is.
profotograaf is dat niet. Niemand host zijn fotografiebedrijf zelf. De klant ziet de binary nooit; die ziet of de galerij laadt op een telefoon in een trouwlocatie met twee streepjes bereik.
Waar het heen gaat
Eigen domeinen zijn live, dus de site van een fotograaf staat op zijn eigen adres in plaats van op een subdomein van mij. Facturatie zit erin. De klantomgeving — overzicht, galerijen, proofing, facturen, berichten — is server-gerenderd op /{slug}/client/, met stateless ondertekende cookiesessies in plaats van een sessietabel.
Wat volgt is meer bedrijfslaag, en meer koppelingen: de boeking die van de vragenlijst weet, de vragenlijst die het contract wordt, het contract dat de factuur wordt. Dat is de hele stelling, en het is precies het deel dat geen enkele combinatie van drie losse abonnementen je ooit gaat geven.