This is Article 3 in my Reverse Proxy Series.
That 30-line proxy from the last article? It works.
But it’s a long way off from something you can deem as production-ready.
Your current proxy is a single if/else statement. It breaks when:
We're going to fix all of that with Hono.js. A tiny framework that makes routing simpler
Hono simplifies our if-else statements and data retrieval from the request. It's tiny, fast, and makes your code readable.
First you need to run bun i hono in your terminal to install the hono.js packages
Hono saves us from writing an enormous chain of if-else statements.
Instead of:
if (url.pathname.startsWith('/blog')) {
// blog stuff
} else if (url.pathname.startsWith('/help')) {
// help stuff
} else {
// main stuff
}You get:
app.get('/blog/*', handleBlog)
app.get('/help/*', handleHelp)
app.get('*', handleMain)Make sure Hono is installed in your repo
Replace your current index.js file with the following and run bun wrangler dev, then open the localhost URL (or press b for the shortcut).
import { Hono } from "hono";
const app = new Hono();
app.get("*", async (c) => {
const { pathname } = new URL(c.req.url);
let res = await fetch("https://jamesbattye.dev" + pathname);
return res;
});
export default app;Ok nice. We’re exactly where we were a minute ago, but in 11 lines of code instead of 30. Brilliant /s
Now we’ll add another project. /blog.
This initial approach will be a bit wordy, then we’ll clean it up after
import { Hono } from "hono";
const app = new Hono();
// Make a variable which holds our projects. This will be easy to add to later
const projects = {
main: "https://jamesbattye.dev",
blog: "https://webflow.com",
};
// if the request is for the blog page
app.get("/blog", async (c) => {
const { pathname } = new URL(c.req.url);
let res = await fetch(projects.blog);
return res;
});
// if the request is for a subpage of the blog folder
app.get("/blog/*", async (c) => {
const { pathname } = new URL(c.req.url);
let res = await fetch(projects.blog + pathname);
return res;
});
// everything else
app.get("*", async (c) => {
const { pathname } = new URL(c.req.url);
let res = await fetch(projects.main + pathname);
return res;
});
export default app;Now we’re saying: if it’s the blog page, get the blog home, if it’s a blog subpage get the blog project + the path, otherwise get the main project.
Not bad, but it’s a bit repetitive. Let's group our requests into a helper function to tidy this up.
import { Hono } from "hono";
const app = new Hono();
const projects = {
main: "https://jamesbattye.dev",
blog: "https://webflow.com",
};
// A helper function that makes the request for us, so we can keep the code tidy
const proxyTo = (target) => async (c) => {
// get the url from the request
const url = new URL(c.req.url);
const targetUrl = target + url.pathname + url.search;
// also we'll clean up the fetch request to include more meta data.
const res = await fetch(targetUrl, {
method: c.req.method,
headers: c.req.raw.headers,
body: c.req.raw.body,
redirect: "manual",
});
return res;
};
// Blog (root + children)
app.get("/blog/", proxyTo(projects.blog));
app.get("/blog/*", proxyTo(projects.blog));
// everything else
app.get("*", proxyTo(projects.main));
export default app;Now you can run bun wrangler deploy and push your new and improved reverse proxy to your worker!
We’ve not got a pretty decent little proxy running, but it’s still not 100% there yet. We’ve still got to resolve these issues:
More on that soon.
In the next ~30 minutes, you'll have two different sites running under one URL.
Reverse proxies are the gateway drug to Webflow Enterprise. Almost every enterprise project I’ve been involved in has implemented it in some form.
Build an automated sync between Webflow's CMS and Algolia's search service using Cloudflare Workers.
If you’ve ever used Webflow’s native background video component and thought “damn, that looks rough” I'm here for you.
As more companies move to Webflow and demand for Webflow Enterprise grows, you’ll see more teams leaning on reverse proxies to solve some of Webflow’s infrastructure limitations.
A small keyboard shortcut can make a marketing site feel faster, more intentional, and “app-like” with almost no extra design or development
A practical, code-heavy dive into GSAP’s utility functions—keyframes, pipe, clamp, normalize, and interpolate—and why they’re so much more than just shortcuts for animation math.
GSAP lets you pass _functions_ as property values. I've known this for a while but never really explored it particularly deeply. Over the last couple of weeks I've been testing, experimenting and getting creative with it to deepen my understanding.
Exploring ways to keep JavaScript modular and maintainable in Webflow — from Slater to GitHub to a custom window.functions pattern. A look at what’s worked (and what hasn’t) while building more scalable websites.