• home
XGitHubSubstackLinkedIn
  • home
XGitHubSubstackLinkedIn

Just write: a site to write

The philosophical side

I made Just Write while reading Rick Rubin's "The creative Act". In it, he describes the creative act as a self-expression, something that the Universe manifests through you. I'm not that philosophical, and unless the Universe was very uninspired that afternoon, a <textarea/> saved on SQLite is a feeble excuse for the Universe manifesting itself.

And yet, I had this idea: the most basic site, dedicated just to writing. How would it look? It would be the most basic writing experience, as close as possible to grabbing a blank sheet of paper and expressing what's on your mind.

No feed to be distracted. No other people's posts. No fonts, color, bold, or sizes (well, to be fair, you have a title). No folders, collaboration or even user accounts. It was an exercise of focus on what would matter. There's that tendency to add new features for some use cases, things that are expected. Maybe it's a way to shield ourselves of the inevitable criticism that, to be fair, is mostly unwarranted: most likely the app will be ignored anyway.

What would a good writing experience have, then? Minimal: a title and what you want to write. A list of things you're writing. Fast and get out of your way faster: you go to the site and automatically create a new document, save it locally, and sync it to the cloud. Then, it's yours to put your thoughts in. It should be able to be resumed from any device and be as seamless as possible but also work locally first.

I caved in, however, in some luxuries: You can toggle full-screen mode from the sidebar, download a .txt if you need to save it somewhere or copy the entire content once you deem it mature enough to promote it to a real (tm) writing app. There is also an "about" section to explain a bit and warn users about sharing the document link.

The good part

What I like about projects like this is taking a problem for a run. I run daily, and there's something I really enjoy doing: state a problem before starting a run and take it with me. I'll think about it, develop it, and try to crack it (some days, I have more luck than others).

The interesting problem with this project was: how can I ensure this is local first but still be synced and picked up seamlessly from another device and back in the original device without losing information? How do I track the latest version of a document?

I bet there are a thousand solutions, but I came up with my own. I'm not claiming it's original, but it's mine: we start with a document with a current version and a next version (the version is a random ID). When the local document syncs with the server, it saves the document content and states to the server the next version ID; then, locally, we set the current version as the next expected version and create a new next version. When we sync the document again, we send the version the server is expecting as next version, set a new next version, and resume the writing.

When a new device gets an existing document, it downloads its content and sets the current local version as the version the server expects. After writing and syncing, the server checks for the expected version, and if it's the same as the new device is sending, saves the document; then the local version generates a new expected version and the cycle starts again.

If there's an older local copy on another device, the local version and the expected version won't match, and the app will prompt the user to choose the correct one. In theory, it should be the server version, but since it's designed to be local-first, the correct version could be the local one. I wanted to avoid taking any chances to do something fancier, but risking losing the user's work.

A nice little thing is that if the versions don't match but the hashed content does, the conflict will automatically be resolved and the sync will continue.

Tech side

I wanted this to be local first and wanted to use IndexDB. It might be overkill for this, as it's only one table with no relationships, but hey, it's my project, my rules.

Also, I'm so in love with SQLite's simplicity: just a file to write to. If I'm going minimalistic on the writing idea, using SQLite was a perfect fit. It's also so darn fast; that last point, however, isn't as relevant as this was designed to be local first, and the sync won't impact the user in any noticeable way... but still, damn.

Finally, I'm all-in into the self-hosted VPS route, which is hosted on a Hetzner server (about $7 a month). There's something about the rawness of putting your files on a folder PC that hits differently than linking your repo and automatically deploying with a push. I'm not advocating doing one over the other, and definitely, DevOps is the least favorite part of all this, but it's like baking your own bread: it just tastes different (maybe, I don't know what a Vercel server tastes like).

The other part is what I'm used to: Vite and React on the frontend (plus tailwind and shadcn because I'm not reimplementing a modal); Node and express for the backend. Everything is in a monorepo with turborepo; Prisma as the ORM as I'm so used to it by working every day; Caddy to serve the front and backend as the total config was around seven lines total, and the automatic HTTPS is a godsend.

Conclusion

Summarizing, three things I'm proud of: first, the DevOps hat isn't as uncomfortable as I once thought (again, not that I like it, but eh); second, I think the versioning stuff is nicely done, and I'm happy with the solution; and last, I'm glad to send to the web an idea that I call my own, with my vision of how it should look, feel and work like, and not let the imagined expectations of theoretical people shape it in a more generalized product: it is what it is, it's the shape I envisioned it to be, and I'm extremely happy that I could realize that idea.

And it's time to let it sail on its own and give its space in my runs to another idea.

(if you’re wondering: yes, I wrote this on Just Write)

Subscribe now

Handwritten signature illustration