Running your SPA on Cloudflare
Your application is ready, you want to prepare for the incoming storm of users, so you separate your SPI server from your UI. To host the UI Cloudflare is chosen, while the API server sits in its data centre. There are a few steps to be had, which are simple, just the AI was acting cute, so I write it down.

Workers & Pages
We shall update our single page application whenever new contenet gets merged into main. All calls to /api/* need to get routed to the API server. These are the steps that wrok. Nota bene: if you want to push from your local machine, the steps are different and a story for another time.
- In Cloudflare head to
Build,Compute & AI,Workers & Pages - Don't get distracted by the blue "Create application" button, you want to click above it and select
+ Addand then Pages (don't select "Worker") - Select "Import an existing Git repository" and select your repo (You need the GitHub integration for that).
- configure your build settings, for a viteJS applicationa, the build command is
npm run buildand the output directory isdist. Save it and you are good to go.
Next step is to configure "Custom domains", so app.example.com points to your page. Follow the UI, it is almost automagic when Cloudflare is your DNS provider
Redirecting /api to the backend
We want Cloudflare to proxy it, not to 30x redirect. A client wouldn't know that the request gets forwarded. This will allow to harden the API (e.g. drop all request not coming from Cloudflare) later on. Also you don't have to deal with CORS.
Search, with and without AI, suggested solutions revolving around files:
- create
_routes.json - code
_worker.js - use an elaborate wrangler setups.
Those all have one thing in common: they didn't work.
What worked: create a folder functions at the root of your repository. Inside add a file [[path]].js - yes, that's two pairs of square brackets and th letters p a t h. It's the catch all for functions. Inside you add:
export async function onRequest(context) {
const { request } = context;
const url = new URL(request.url); // Test endpoint
if (url.pathname === '/helloworld') {
return new Response('Workers of the world unite!', {
headers: { 'Content-Type': 'text/plain' }
});
} // Proxy API requests
if (url.pathname.startsWith('/api/')) {
url.hostname = 'api.example.com';
url.port = '4443';
url.protocol = 'https:';
return fetch(url, request);
}
return context.next();
}
Update
Instead of creating functions/[[path]].js, create functions/api/[[path]].js and simplify the function:
export async function onRequest(context) {
const { request } = context;
const url = new URL(request.url);
url.hostname = 'api.example.com';
url.port = '4443';
url.protocol = 'https:';
return fetch(url, request);
}
Hardening the setup is a story for another time.
As usual YMMV
Posted by Stephan H Wissel on 20 January 2026 | Comments (0) | categories: Cloudflare WebDevelopment