← IndexCase studies — 04 / 04Live site ↗
Author site · CMS · freelance, solo buildSep 2026 — ongoing

A site the poetkeeps himself.

Marc Bonaventure is a French poet who has lived in Pará, in the Brazilian north, since 2001, and writes about it in Portuguese. He had three books, a growing agenda of readings and nowhere to send anyone. The site is in Portuguese, and the whole point of it is that it stays current without me: behind a password there is a small admin where he posts photographs, video and the dates of his next readings.

Client
Marc Bonaventure
My role
Freelance developer, solo
Team
Just me, with the author
Timeline
Sep 2026 — ongoing
Marc Bonaventure — screenshot

01 — The problem

A reader after the trilogy, the next reading or photographs from a launch had to dig through Instagram posts and a publisher's catalogue page. There was no single address to give a journalist, a bookshop or a festival.

A site he could not update himself would have been worse than none at all. Readings are booked and moved with a few days' notice, and he has no developer on retainer; anything that needed me in the loop would have gone stale inside a month.

The real constraint

The person maintaining this site is a poet, not a developer, and usually on a phone. If posting a photograph from last night's reading takes more than a couple of minutes, it will not happen.

02 — My role

Everything: schema, back end, front end, the admin, deployment, and the conversations about what he would actually use. A solo freelance build, in his language rather than mine.

Data model, Prisma schema and migrations

Public site in Brazilian Portuguese: books, agenda, gallery, press, contact

Password-protected admin for events, media and site copy

Image and video pipeline, and the storage drivers behind it

Deployment, structured data and SEO

Decisions & trade-offs

04 — what I chose, what I gave up

01

One app, run twice

Next.js · APP_ROLE

The public site and the admin are two instances of the same application, with one environment variable deciding which routes each answers: the public instance 404s /admin, the admin instance sends everything else back to the login. Same code, same database, same uploads.

Trade-offA genuinely separate admin would have needed an API built for it first, since the admin is server-rendered and talks to Postgres through Prisma directly. Two roles buy the same separation of addresses for the price of a config flag, though what they separate is routes, not processes.

02

Books in code, events in the database

Content modelling

The admin started with a section for poems and books, and it came back out. A book has an edition history, a photographer, a preface writer and an imprint, none of which the schema had room for, and a new one arrives every year or two. The books are authored in the codebase; the database holds only what actually changes: events, images and a few pieces of copy.

Trade-offA new book means a deploy. That is roughly once a year, weighed against an admin form nobody would remember how to fill in.

03

Storage behind one interface

Cloudinary · R2 · sharp

Every upload goes through a single storage interface, which is why the driver behind it could change twice without the upload path changing: local disk while building, then Cloudinary once it was hosted somewhere with no persistent volume. Video goes straight from the browser to R2, because a serverless function will not take a body over 4.5MB and a phone clip passes that ten times over.

Trade-offAn abstraction over something a single file write would have done on day one. It paid for itself the first time the host changed.

04

Time is Belém's, not the server's

Timezones · agenda

Every date is formatted through one module that pins the timezone to Belém, and the admin's date fields go through matching parse and format helpers. Constructing a date directly uses whatever timezone the process happens to run in, which quietly turns a 19h reading into a 22h one on a UTC host.

Trade-offTwo helpers to remember instead of the standard library. The alternative is a reader turning up three hours late to an empty room.

04 — How it’s built

Front end
Next.js 16 · React 19 · TypeScript · hand-written CSS
Admin
Mantine 9, server actions, one password
Data
Prisma 7 · PostgreSQL
Media
sharp for images, ffmpeg for video, swappable storage driver
Delivery
Solo build, scoping to launch

05 — Building for one non-technical owner

The stack was not the hard part. Deciding what Marc should be able to change, and making the rest impossible to break, was.

One door
One author, one password, a hash and a signed cookie. No user table, no roles, no invitations, because there will never be a second user.
Fails politely
Where video encoding is unavailable, an upload explains itself and asks him to tell whoever maintains the site. Photographs carry on working.
Whitespace is content
Verse is rendered so that indents and stanza breaks survive exactly as typed. Trimming a poem's whitespace is a bug, and the README says so in as many words.
In his language
The site, the admin and its error messages are all in Brazilian Portuguese, including the parts only he will ever see.

06 — What I’d do next

Real content. The gallery still has placeholder slots and the agenda is empty until the next reading is booked. The site is live and waiting on Marc rather than on me.

An iCal feed for the agenda and an RSS feed for the site, so a bookshop or a festival can subscribe instead of checking.

The poem and collection tables are still in the schema with nothing writing to them. They should either earn their place in a proper data-model pass, or come out.

← Indexderoubaix.marisha@gmail.comMarisha Deroubaix — 2026