Blink: The CMS I Built Because I Was Tired of My Own Workflow
How a 10-minute content update became a 30-second Notion edit, and why I'm open-sourcing it at blink.saharbarak.dev
The 2 AM Realization
It was 2 AM on a Tuesday. I had just shipped a client project and wanted to add it to my portfolio. Simple, right?
I opened VS Code. Found FeaturedWork.tsx. Scrolled to the hardcoded array of projects. Added the new one. Committed. Pushed. Waited for Vercel to rebuild.
Seven minutes later, my portfolio finally showed the new project.
That's when it hit me: I'm a developer who builds tools that save people time, and yet I'm wasting my own time on the most basic task imaginable - updating my own website.
Something had to change.
The Absurdity of My Old Setup
Let me paint you a picture of what my portfolio looked like under the hood:
- FeaturedWork.tsx - 4 hardcoded projects
- FreelanceWork.tsx - 5 client case studies
- Research.tsx - 6 research papers
- OpenSource.tsx - GitHub repos as strings
- now/page.tsx - What I'm doing now (rarely updated)
- ideas/page.tsx - Ideas page (embarrassingly outdated)
- config/links.ts - 30+ URLs scattered everywhere
Every. Single. Change. Required:
- Opening my laptop (goodbye mobile ideas)
- Navigating a maze of files
- Editing TypeScript (hoping I don't typo a prop)
- Git commit, push
- Praying Vercel doesn't choke
- Waiting... waiting... deployed.
For a text change.
The irony? I'd get ideas for blog posts while on the train. But by the time I got home, opened VS Code, and remembered which file to edit... the inspiration was gone. Dead. Buried under friction.
What I Actually Needed
I sat down and made a list. Not what would be cool, but what I actually needed:
Must Have:
- Update content from my phone (ideas don't wait for laptops)
- See changes instantly (not "in 3-5 minutes")
- Keep my TypeScript type safety (I'm not going back to any)
- Support 9+ different content types
- Zero rebuilds for content changes
Nice to Have:
- Rich text editing for blog posts
- Familiar interface (no learning curve)
- Markdown support
Absolutely Not:
- Monthly fees that scale with my content
- Another login to manage
- Self-hosted database nightmares
- Vendor lock-in
The Notion Epiphany
Here's the thing: I already use Notion. Every day. For notes, for project management, for random 3 AM thoughts.
The mobile app is phenomenal. The editor is the best I've used. And crucially - I already have muscle memory for it.
What if... Notion was my CMS?
The idea seemed crazy at first. Notion isn't designed to be a CMS. The API has rate limits. Queries are slow. There's no real-time updates.
But what if Notion was just the editing layer? What if something else handled the serving layer?
Enter Convex.
The Architecture That Changed Everything
Convex is a real-time database with a twist: it's serverless, type-safe, and genuinely reactive. When data changes, connected clients update instantly. No polling. No WebSockets to manage. It just works.
Here's what I built:
Notion (I write here) --> Sync Script (Bridge) --> Convex (Site reads) --> Next.js App (You see this)
The flow is simple:
- I write in Notion - Using the editor I already know and love
- Sync script pulls data - TypeScript script fetches from Notion API
- Convex stores it - Real-time database with proper schemas
- Site reads from Convex - Instant queries, real-time updates
The magic? Once data is in Convex, my site doesn't know or care about Notion. It's just querying a fast, real-time database. Updates happen in milliseconds, not minutes.
Building the Schema
I needed to model 10 different content types. Not one generic "content" table - that's a recipe for type safety disasters. Each type got its own schema:
Projects - The big portfolio pieces
- Custom color schemes per project
- Logo support
- Order control
- Published flag for drafts
Blog - Full articles with content
- Full markdown content
- Tags and categories
- Cover images
- Slug-based routing
And eight more: Freelance (client work), Research (papers), Contributions (open source), Ideas (concepts), Now (current activities), About (bio), Availability (booking status), Site Copy (microcopy).
Each table has indexes for the queries I'd run: by_order, by_published, by_notion_id.
The Sync Script: Where the Magic Happens
The sync script is deceptively simple. It's just fetching from one API and writing to another. But the details matter.
The Tricky Part: Notion's Property Types
Notion's property types are wildly inconsistent:
- Title is nested differently than rich_text
- Multi-select returns an array of objects
- Dates have their own structure
- Each requires different extraction logic
For blog posts, I needed the actual content, not just properties. That's a separate API call to fetch blocks.
The Upsert Pattern
The key is the notionId field. Every record tracks which Notion page it came from. On sync, we check if the record exists - if yes, update. If no, insert. No duplicates. Updates are clean.
The Before and After
Before Blink:
| Task | Time | Friction | |------|------|----------| | Add new project | 7 min | Open laptop, find file, edit code, commit, push, wait | | Update availability | 5 min | Same dance | | Write blog post | 15 min | Find template, copy structure, edit, pray | | Fix typo | 5 min | Full deployment for one character |
After Blink:
| Task | Time | Friction | |------|------|----------| | Add new project | 30 sec | Open Notion, fill in fields, done | | Update availability | 10 sec | Toggle a dropdown | | Write blog post | However long the writing takes | Just write in Notion | | Fix typo | 10 sec | Edit in Notion |
The sync script runs in under 5 seconds. The site updates instantly after sync. No rebuilds. No deployments. No prayers.
Discussion
Sign in to join the conversation