I Spent 3 Hours Trying to Proxy a Blog Subdomain. Here's My Descent Into Madness.
All I wanted was vibed-lab.com/blog. What I got was an education in why the internet is held together with duct tape.
Series: VIBE.LOG
- 1. The Layout Vocabulary Cheat Sheet: What to Call That Thing on Your Screen
- 2. I Spent 3 Hours Trying to Proxy a Blog Subdomain. Here's My Descent Into Madness. β you are here
- 3. The Complete SEO Guide: How to Make Google Actually Notice Your Website
- 4. Why Your Next.js Favicon Isn't Showing (And the Three Ways to Actually Fix It)
- 5. GitHub Keeps Telling Me My Branch Is Fine. And Also Not Fine. At the Same Time.
- 6. Mobile-First Playground: Making an Astrology Grid Actually Work on a Phone (And Go Viral While Doing It)
- 7. Playground Is Live: The Destiny Grid, Real Astrology, and Why I'm Shipping a Toy Every Month
- 8. The Interactive Component Cheat Sheet: What to Call That Clickable Thing
- 9. Google Rejected My Site for 'Low-Value Content.' Here's What I Actually Fixed.
- 10. I Actually Fixed Everything. Here's What That Looked Like.
- 11. I Hired 131 AI Employees Today. Here's How.
- 12. I Let My AI Run 72 Backtests While I Watched. It Picked the Winner.
- 13. I Taught My AI to Stop Asking Questions. It Took Five Rewrites.
- 14. Obsidian Turned My Scattered Notes Into a Second Brain. Here's How to Set It Up.
- 15. The Destiny Grid Gets Its East Wing: I Rebuilt Saju (εζ±ε «ε) in TypeScript
- 16. Molecule Me: Your Personality, Encoded in Chemistry
- 17. OpenAI Just Built a Plugin for Their Competitor's Tool. I Installed It.
- 18. I Combined Two Open-Source Repos Into an AI That Plans, Builds, and Reviews Its Own Code
- 19. I Built a Weekly Directory for Claude Code Agents (Because My Brain Couldn't Keep Up)
I just wanted one thing.
vibed-lab.com/blog. That's it. The whole blog, accessible from my main domain, URL intact. Clean. Professional. AdSense-friendly.
Three hours later I had touched Cloudflare Workers, DNS records, Vercel config, redirect rules, _redirects files, next.config.js, and my own sanity. None of it worked.
This is that story.
π The Plan (It Seemed So Simple)
The setup: Hashnode headless blog deployed on Vercel at vibed-lab-portal.vercel.app. Main site at vibed-lab.com on Cloudflare Pages. The goal was to proxy /blog requests from the main domain to the Vercel app β URL stays the same, content comes from Vercel.
This is a completely normal thing to want. People do this all the time. There are Stack Overflow answers about it. There are blog posts about it.
Reader, those blog posts lied to me.
π§ Step 1: The Cloudflare Worker (Works! Kind Of.)
First attempt: a Cloudflare Worker to intercept /blog requests and fetch from Vercel.
export default {
async fetch(request) {
const url = new URL(request.url);
if (url.pathname.startsWith('/blog')) {
const targetUrl = 'https://vibed-lab-portal.vercel.app' + url.pathname + url.search;
return fetch(targetUrl, request);
}
return fetch(request);
}
}The blog loaded. Sort of. The layout was completely broken β like a webpage from 2003 but worse, because at least 2003 websites were trying to look bad.
π¨ Step 2: CSS Is Apparently Optional Now
Dev tools revealed the problem: vibed-lab.com/_next/static/css/b17cced2663f0266.css was returning 404. The Worker Route only covered /blog*, so /_next requests were going straight to the main Cloudflare Pages app, which had no idea what to do with them.
Okay, add more routes:
vibed-lab.com/_next*vibed-lab.com/static*
Still broken. Turns out Cloudflare Pages has higher priority than Workers for static asset paths. The Pages app was intercepting /_next before the Worker even had a chance.
This is the part where a reasonable person would stop and reconsider the architecture. I am not always a reasonable person.
π¦ Step 3: assetPrefix to the Rescue (Not Really)
Next move: tell the Vercel blog to load its CSS/JS directly from vibed-lab-portal.vercel.app instead of the current domain.
const nextConfig = {
assetPrefix: 'https://vibed-lab-portal.vercel.app',
}Deployed. Refreshed. CSS loaded! Progress!
But now most of the page content was gone. Network tab showed /blog/ping/data-event returning 500. Hashnode's internal analytics endpoint was firing at a path that didn't exist on Vercel. Cool. Great. Love that for me.
π¨ Step 4: The Redirect That Wouldn't Die
At some point I noticed blog.vibed-lab.com was redirecting to vibed-lab.com/blog. Fine, that makes sense β except it was creating a loop. So I deleted the redirect rule from Cloudflare.
It kept redirecting.
Checked Page Rules. Nothing. Checked the Worker code. Nothing. Had Claude Code audit the entire project. Nothing. The redirect was a ghost. It existed nowhere and yet it persisted, like a passive-aggressive coworker who won't stop forwarding emails even after you ask them to stop.
Eventually found it: the blog.vibed-lab.com CNAME was pointing to vercel-dns.com. Vercel didn't recognize the domain, so it was silently redirecting everything on its own. Deleted the DNS record. Redirect stopped.
This is why we check DNS records.
π Step 5: The _redirects File (Narrator: It Did Not Work)
By this point I had abandoned the Worker approach entirely and pivoted to Cloudflare Pages native _redirects:
/blog/* https://vibed-lab-portal.vercel.app/:splat 200
/blog https://vibed-lab-portal.vercel.app 200Status code 200 means proxy β URL stays the same, content comes from the target. Deployed. Opened vibed-lab.com/blog.
"This site can't be reached."
Turns out Cloudflare Pages _redirects with status 200 does not support external domains. This is technically documented somewhere. I did not read that part of the documentation. Switched to 302.
Now the root domain (vibed-lab.com) was serving the blog. Because the Worker Route vibed-lab.com/* was still catching everything. Of course it was.
π₯ The Ending Nobody Asked For
After three hours, the final tally:
- Cloudflare Workers: touched β
- DNS records: deleted and recreated β
- Vercel config: modified β
- Redirect Rules: deleted β
- Page Rules: checked and found innocent β
_redirects: created, deployed, failed βassetPrefix: configured, partially helpful β- My will to live: intact, but diminished β
The actual solution? Scrap Hashnode entirely. Build a real Next.js blog inside the main project. /blog is just a route now.
No proxies. No Workers. No _redirects. No external domains. No ghosts.
In retrospect, the subpath proxy approach has a fundamental problem: every CSS file, every JS bundle, every analytics ping, every internal API call from the external app needs to either be rewritten or proxied separately. It's not impossible β but it's fighting against how the web works, and the web usually wins.
The three hours weren't wasted though. I now understand Cloudflare Worker priority, Pages routing behavior, DNS propagation quirks, and exactly why every "just use a reverse proxy" tutorial glosses over the implementation details.
Turns out "just proxy it" is the "just add a login page" of infrastructure advice.
Written by
Jay
Licensed Pharmacist Β· Senior Researcher
Building production-grade AI tools across medicine, finance, and productivity β without a CS degree. Domain expertise first, code second.
About the author βRelated posts