I genuinely thought the hard part was over.
I spent four weekends building the backend. Authentication. Validation. Firestore. APIs. Clean architecture. All the little developer rituals that make you feel like you’re building something serious instead of slowly losing your mind in a code editor.
It finally worked.
I deployed it to Railway, pointed my portfolio at it, and sat back expecting that sweet, rare feeling of being done.
The universe immediately laughed in my face.
Turns out, writing a backend is one thing.
Getting four different cloud services to stop acting like they’ve never met each other is apparently a completely different profession.
Chapter One: Railway Actually Behaved
To Railway’s credit, deployment was almost offensively easy.
Connect GitHub.
Set environment variables.
Click Deploy.
Done.
That’s it. That’s the whole thing. No ritual sacrifice. No cursed terminal command. No “just one more config file” nonsense.
For about five glorious minutes, I genuinely believed backend deployment was easy. I even deployed my wife’s backend afterward because I was feeling dangerously competent.
I had become that developer.
“Deploying? Yeah, it’s pretty straightforward.”
Past Me was an idiot. A confident idiot, but still.
Chapter Two: Cloudflare Had Other Plans
The frontend lived on Cloudflare Workers.
The backend lived on Railway.
Simple enough, right?
Wrong. Obviously wrong.
Everything worked locally.
The API worked.
The homepage worked.
The backend responded exactly how it should.
And yet...
The blog list worked.
The featured articles worked.
The recommendations worked.
But every individual blog article returned a 404.
Not from the backend.
Not from Railway.
Not even from Cloudflare, which would’ve at least been honest about it.
No, it was coming from somewhere in the void. Some dark corner of the stack where bugs go to breed.
Fantastic.
Chapter Three: The Ghost Hunt
This was where the real debugging started. The kind of debugging where you stop trusting reality and start assuming every layer of your stack is lying to you.
I deleted caches.
I purged Cloudflare.
I rebuilt OpenNext.
I removed .next.
I removed .open-next.
I redeployed.
Again.
Again.
Again.
At some point I started searching compiled output for the dreaded localhost:3001, because apparently my brain had decided we were doing archaeology now.
And sure enough...
There it was.
Cue immediate panic.
I was already mentally rewriting my entire deployment pipeline, preparing for a week-long war against environment variables and bad assumptions.
Then I checked the obvious thing I should’ve checked first.
It was my .env.local.
A file that wasn’t even committed.
A private little gremlin of a file sitting there the whole time, quietly ruining my life.
And honestly, that was also the moment I realized something else: I had already migrated the frontend to consume the backend. I knew it because I was in the middle of deleting the old Firebase variables. That was the clue. The old Firebase setup was still hanging around in my environment, and I was literally cleaning it out while trying to figure out why production was acting possessed.
Thank you for the emotional damage.
Chapter Four: Next.js Gaslights Me
Production logs were incredibly helpful in the way a brick to the face is “helpful.”
Amazing.
Thank you, Next.js.
That tells me absolutely nothing except that something somewhere is unhappy and I should probably go stare at the ceiling for a while.
Meanwhile:
- Blog list endpoint? ✅
- Recommendations endpoint? ✅
- Local article fetch by slug? ✅
- Production article fetch by slug? ❌
Local development was perfectly happy fetching:
The blog list routes also worked fine in production:
And recommendations were fine too:
But the slug route in production?
No explanation.
No context.
Just a cold, mechanical refusal to function like a normal application.
Chapter Five: Redirects That Didn’t Exist
Eventually I found something interesting.
Now we were getting somewhere.
Or so I thought.
Because then I tested it properly:
Running curl?
Worked.
Opening the API in Chrome?
Worked.
Fetching locally?
Worked.
Only Cloudflare Workers hated it.
Which meant the redirect wasn’t really a redirect in the normal human sense. It was infrastructure doing infrastructure things, which is always code for “some invisible layer is being dramatic.”
Those are my favorite kinds of bugs.
The ones where every system insists it’s innocent while the whole thing is clearly on fire.
Chapter Six: The Simplest Fix Was the Right One
Originally, everything fetched through the public API hostname behind Cloudflare.
That meant the blog list routes, the recommendations route, and the slug route were all going through the same path:
That worked for the blog lists:
It worked for recommendations:
And it even worked locally for the slug route:
But deployed, that slug route was the one that kept falling apart and returning 404.
So here’s where the fix came in:
We used the direct Railway endpoint only for the blog slug route.
Just that one route.
Everything else could keep using the public API hostname, but the per-article fetch had to go directly to Railway instead of routing through Cloudflare.
In other words, the slug request needed to hit something like:
And somehow that fixed everything.
No SSL changes.
No DNS edits.
No Worker rewrites.
No Cloudflare wizardry.
No “enterprise-grade” nonsense.
Just bypass the fancy hostname for the one route that was causing trouble and go straight to the backend like a civilized person.
Fifteen minutes later...
Zero errors.
Every article loaded.
Production was happy.
I stared at the screen wondering why I had just spent several hours arguing with networking like it owed me money.
Things I Accidentally Learned
Deploying software is not really about writing software.
It’s about convincing twelve different services that they are all part of the same team, even though none of them trust each other and one of them is definitely lying.
Sometimes the code is correct.
The backend is correct.
The frontend is correct.
The DNS is correct.
The certificates are correct.
And somehow everything is still broken.
Because computers have collectively decided that today is your character development arc.
The Backend Saga Continues
Looking back, building the backend wasn’t actually the hard part.
Getting Railway...
Cloudflare...
OpenNext...
Workers...
DNS...
Environment variables...
Static generation...
Server-side rendering...
...to all shake hands politely without immediately setting each other on fire was the real project.
I now understand why senior developers answer deployment questions with a thousand-yard stare.
They’ve seen things.
They’ve watched “simple” infrastructure turn into a three-hour investigation because one route had an attitude problem.
If nothing else, this project taught me one important lesson.
The bug is never where you think it is.
Unless it’s a forgotten .env.local.
Then it’s definitely that.
At least this time.