Published on

Andika Online

This is probably one of those projects only I will ever use, but it was still worth it.

Background

A while back, I came across this online writing tool called blank.page. Not sure what I was searching for when I stumbled upon it, but the moment I started using it, I knew I'd become a regular. What really hooked me was how simple it was to get started. You open the site, and you immediately can start typing. No login required! 🤯

blank.page

At first, I thought it was strange. Where are my notes being saved? The dev tools showed that there were no network calls being made, but whatever you wrote down was persisted between sessions. I checked out the browser storage, and there were my notes in local storage. A very simple approach to a very useful tool.

I mostly used blank.page for rough work. I'd jot down quick todo lists, random things I'd want to remember, links that I'd want to circle back to. I liked it. You open the page and milliseconds later there's everything as you left it. So snappy, the beauty of local/offline-first applications.

At the time, I also used Notion for more heavy duty work, but I'd find myself opening blank.page as a default if I needed to jot something down quick. It was almost perfect.

What it lacked however was, you were only limited to doing everything on one page. You couldn't add photos. And even links weren't clickable. These were important though I don't think I was using it as intended. I wanted it to be a space where I QUICKLY dumped everything I might need for later. It's probably meant as a distraction-free writing app for writers, not a brain dump.

So what Notion had (brain dump capacity), blank.page lacked. And what blank.page had (local-first snappiness) Notion lacked.

I figured it wouldn't be so hard to build my own version that solved some of the missing parts, especially considering everything happens locally.

My Spec

  • It had to be local/offline-first - using browser storage for snappiness and maybe online storage as backup
  • I should be able to dump photos, links and potentially videos
  • I should be able to add multiple pages/notes
  • I should be able to organize my notes ideally with tags and folders - but not in a way that's too sophisticated I can confuse myself. #Notion #deepnesting.
  • I should be able to backup my data online incase I clear my browser cache or I'm using another computer

Building It

A few weekends later, here is where it's at...

I setup the project using react-vite. Apart from syncing the data online, I finished the core spec pretty fast. I chose to save data on IndexDB because of its higher storage capacity and chose DexieJs as a database manager as it makes working with IndexDB a breeze.

For the editor, I ended up settling for Novel.sh for its many features and Notion-like style. Also the fact that I can embed Youtube videos by just pasting (such a hack).

andika.online

Online storage was where the work was at. I first started with using Google Drive to backup data, but I noticed it'd take ages for syncing to complete. You can't just update a single record, you have to update the whole list of pages as a JSON which was annoying.

I then tried Firestore but it turned out some of my notes exceeded Firestore's document size limit of 1MB. I finally settled on Supabase. No limitations there, it being a postgres database. Considering I don't expect any users, I'll probably be using it for free 😎.

How Syncing Works

Here is a quick overview of how syncing worked:

  1. You'd have to be logged in so that your notes can be linked to you.

  2. All the user edits would be recorded on IndexDB, but upon changing the page, or leaving the browser, updates to the server are made in the background.

  3. During syncs, the following would happen:

    • If loading data for the first-time, say to a new device, the data would be pulled from the remote database and saved locally.
    • For consecutive syncs, this would entail pulling the updates since the last sync time from the backend and comparing it with local data updated after the same time.
    • I'd then merge the 2, with the record with the latest updated version always winning conflict resolution. Then I'd do the relevant write-requests to the backend and IndexDB syncing

I had also explored tools such as ElectricSQL, but for this use case, I was really prioritizing simplicity. I still kept the Google Drive backup feature as an option though.

The online syncing gave way to a means of sharing notes and collections with the public. So I added a /shared route from which I would load data from the backend (not locally). From this route, the app used react-query for querying and handling state management for the shared collections and notes. I also had this route configured to be accessible from a user's chosen subdomain for personalisation. For example, https://denis.andika.online shared-notes

State Management

State management was also interesting...

With potentially hundreds of collections and notes being stored locally (I store 1200 atm), I had to optimize for speed and memory usage. I wanted the app to feel snappy and instantaneous.

Here's how I handled it:

state-management
  • I preloaded and stored metadata (just titles, tags, etc no content) from IndexDB into Zustand. This facilitated navigation. I would only load the full content on demand since the content was heavier. When navigating away, I made sure to clear memory to free resources.

  • Content updates would update Zustand stores and IndexDB immediately (or after debouncing).

    On page changes, background synchronization (if logged in) would be initiated to the remote database by the sync engine as shown above.

    A repository pattern was used to abstract database calls. This helped/will help when switching databases eg moving to self-hosting by standardizing the core logic.

    The context layer was used to orchestrate the flow of data between repositories and zustand stores.

Future Plans

  • This would probably work better as a desktop app.

    Saving data on the browser can be flaky and it can disappear anytime. There's also the question of security. Anyone with access to your browser has access to your notes unless you clear your data. Then pull it next time, which is annoying.

    But then again, anyone with access to your browser has access to your emails, unless you logout. (We're all not perfect)

    Making this a desktop app would probably entail wrapping the app with Electron or Tauri. Should be interesting. I'll probably do this the next time I feel inspired.

  • I'll switch to self-hosting eventually

    Supabase worked well for quickly setting up a syncing database for this project, but I prefer having the data on a server I fully control.

Conclusion

All in all, I did all this just to be able to experience local-first snappiness on my collection of notes.

There's a reason why apps like ExcaliDraw and Linear are loved by many. Local-first snappiness really helps with user experience. The local-first scene is a whole movement and is worth checking out.

This tool was me dipping my toe into this world, and I love using it. It's honestly transformed how I learn and manage my notes. My DIY approach to avoid using Notion.