"I was only supposed to move my blog off a direct Firebase REST setup."
If you've been following this little collapse in slow motion, you already know how this started:
"Maybe my portfolio should have a backend."
That was the entire plan.
Not a grand architecture rewrite.
Not a CMS.
Not a week-long descent into feature creep and self-inflicted psychological damage.
Just a backend.
A tiny, harmless backend.
The original setup was simple in the way a trap is simple: a React dashboard talking directly to Firebase, and a Next.js frontend also reading from Firebase like nothing could possibly go wrong. It worked. Technically. Which is usually how these things seduce you before they ruin your sleep schedule.
Looking back, even before the Firebase REST setup, I had already integrated the Hashnode API. I didn't even have my own dashboard back then. It was like using someone else's refrigerator to store my hot dogs and pretending that counted as infrastructure.
After surviving production — which immediately punished me for being optimistic — I genuinely thought I was done.
My dear, deeply misguided friend...
I was not done.
Step 1: "Let's just connect the frontend."
The backend was deployed.
Railway was alive.
Cloudflare Pages was talking to the API without throwing a fit.
I figured I’d swap a few fetch calls, pat myself on the back, and go pretend I had hobbies.
Instead, I rewrote basically every blog service.
Pagination.
Categories.
Sorting.
Search.
Retry logic because Railway free instances apparently enjoy hibernating like emotionally unavailable bears.
Fallback logic because Firestore decided ascending indexes are a luxury reserved for people with better life choices.
Nothing says “deployment complete” like immediately building more infrastructure because the first infrastructure now needs infrastructure.
Then the Dashboard Happened
Once the API existed, the admin dashboard stopped being a weird Firebase side quest and became useful.
No more manually poking Firebase through REST every time I wanted to create or update content.
No more treating every tiny content change like it deserved a deployment ceremony and a candlelight vigil.
I could create drafts.
Publish them.
Edit them.
Change status.
Manage categories.
Everything flowed through the API like a civilized system instead of whatever cursed arrangement I had before.
At some point I stopped calling it “the dashboard” and had the horrifying realization:
"...oh."
"I built a CMS."
Nobody warned me this would happen. If they did, I would have ignored them anyway, because apparently I enjoy learning things the hard way and then documenting the damage.
Search Was Supposed To Be Easy
Firestore doesn’t support partial text search.
So naturally I decided to make search anyway, because apparently I have a personal vendetta against peace.
The backend now fetches published articles, performs an in-memory search across titles, excerpts, and tags, recalculates pagination, and returns valid paging metadata.
For a portfolio-sized blog, it’s fast, simple, and honestly kind of elegant.
Then I added:
debouncing
stale request protection
request IDs
loading states
floating search
sticky filters
animated loading indicators
At that point the search box had received more engineering attention than some entire products.
And while I was literally writing this article, I realized the debounce was technically fine but also kind of annoying — especially on mobile. You could still be typing and the site would already be trying to interpret your intentions like some desperate oracle with a keyboard.
So I changed it mid-article and switched to an explicit search button instead.
Because apparently even my blog posts now generate feature requests.
A Small Mid-Article Detour
While I was writing this section, I ended up changing the search flow because the debounce felt a little too eager.
BlogFilters.tsx
I changed the search input so it behaves more like an actual form now. Instead of trying to be clever on its own, it accepts an onSearchSubmit prop, turns the magnifier into a clickable button, and shows a spinner while a search is running. Pressing Enter does the same thing, and the little × button now appears whenever there’s text in the field — not just while fetching — which makes it easier to clear the input and try again without wanting to launch the laptop into the sun.
BlogList.tsx
This is where I ripped out the debounce logic entirely. I deleted searchDebounceRef, isFirstRawSearch, and the debounce useEffect, then replaced them with a simple handleSearchSubmit function that only commits rawSearch → search when the user explicitly submits. I also passed that handler through sharedFilterProps, and added a small result count line under the filters so the page can say things like "Searching…", "No results found", or "24 results found" depending on what the hell is happening.
FloatingBlogFilters.tsx
This one was mostly plumbing, but still necessary: I added onSearchSubmit to the props type so the floating filter bar can pass it straight through to BlogFilters without breaking the shared filter setup.
Then I Looked At The Blog List...
"...it would be nice if the filters stayed visible."
Normal people would make them sticky.
I merged them into my global floating breadcrumb navigation instead.
Using a React Portal. Well, I'd like to think I was thinking ahead when I built
Because apparently I can’t leave a simple UI problem alone unless I’ve made it slightly more theatrical and much more annoying to explain later.
Now the search bar quietly slides into the breadcrumb while you scroll.
Nobody asked for this.
I certainly didn’t need it.
I love it anyway.
Then I Opened An Article
This was where things really started getting stupid.
What began as:
"Maybe I should add Previous and Next articles."
turned into:
Related Articles
Reading Progress
Table of Contents
Sticky desktop TOC
Collapsible mobile TOC
Scroll Spy
Heading anchor links
Copy Link
Resume Reading
Comments
Reading history
Somewhere along the way I stopped building a blog.
I accidentally built a reading experience.
Which is a much fancier way of saying I kept opening one more rabbit hole and then acting shocked when it swallowed me whole.
My Favorite Moment
The funniest realization came while I was planning future features.
I had written down things like:
View counts
Trending
Featured articles
Series
Scheduled publishing
SEO overrides
Canonical URLs
Then I stopped.
Not because I couldn’t build them.
Because I realized...
I already had enough.
For the first time since starting this project, I wasn’t being held hostage by architecture anymore.
The backend wasn’t blocking me.
The frontend wasn’t blocking me.
The CMS wasn’t blocking me.
The only thing left was writing.
Which is both the point and the part I kept avoiding by building more systems like a sleep-deprived raccoon with a keyboard.
That’s a weirdly satisfying place to end up.
The Unexpected Win
The biggest success wasn’t Hono.
Or Railway.
Or Firebase.
Or Cloudflare.
It was finally separating content from code.
Adding a new article is no longer a deployment.
It’s just...
writing.
That’s exactly what I wanted when this project started.
I just didn’t realize I’d end up building an entire publishing platform on the way there like some kind of overcaffeinated accident with a GitHub account.
What I Actually Built
When I look back at the last couple of weeks, this little “backend migration” quietly became:
A Hono API
Firebase repositories
Authentication and authorization
A full CMS
Categories
Comments
Search
Sorting
Pagination
Related articles
Previous / Next navigation
Reading progress
Resume reading
Sticky table of contents
Copy link
SEO metadata
Structured data
Retry logic
Production deployment
Cloudflare Pages integration
Railway hosting
It started with a React dashboard and a Next.js frontend both talking directly to Firebase.
It ended with a publishing platform.
...and honestly?
I’m finally done.
Probably.
Please don’t let Future Maiko read that last sentence.