What's happening
You wrote a backend function called processOrder. It works in the editor. The preview can call it. You deploy. In production, your form submits, the frontend calls /functions/processOrder, and the response is 404. Or worse, 405 Method Not Allowed. Or worst, an HTML page that looks like your homepage.
A user described the pattern on the feedback board: "Router incorrectly processes /functions/* paths as frontend routes rather than directing them to backend Deno runtime." Another reported it after a function rename: the old name still half-worked, the new name 404ed, and a redeploy was the only fix.
When this hits in production, every feature that depends on a backend function dies at the same time. Saves fail. Webhooks fail. Auth flows that proxy through a function fail. The frontend keeps loading because the SPA shell is fine, which makes it look like a partial outage even though everything backend-dependent is down.
Why this happens
Base44 deploys run two sub-deploys: the frontend bundle and the function manifest. The platform router needs both. If the function manifest deploy succeeds but the router-table refresh lags or fails silently, the router does not know /functions/processOrder should go to the Deno runtime. Without a known route, the request falls through to the SPA shell, which serves your index.html and returns 405 on POST.
This produces three observable failure modes.
405 on POST, SPA HTML on GET. The router is treating the function path as a frontend route entirely. The function is defined and would run if reached, but the router never forwards.
Intermittent 404. The route is partially registered. Some edge nodes have the new manifest, others do not. Requests round-robin between working and 404. This is the worst variant because it looks like a flaky function rather than a routing problem.
Stale route after rename. You renamed oldFunc to newFunc. The router still routes oldFunc to a now-deleted file (404 with stack trace) and does not yet know about newFunc (404 from SPA shell). Both names are broken in different ways.
The root cause is a race in the deploy pipeline between function-manifest publication and router-table refresh. Base44 does not publish a precise sequence diagram, but the symptoms are consistent across enough user reports to confirm the pattern.
Sources: feedback.base44.com post "Critical Platform Bug — Function Routing Broken (POST 405)", docs.base44.com/Community-and-support/Troubleshooting, Hacker News thread on Base44 reliability (news.ycombinator.com/item?id=44316920).
How to reproduce
- Create or pick a Base44 project with at least one backend function. Use a simple POST handler.
- Deploy.
- Confirm the function works:
curl -X POST https://yourapp.base44.app/functions/yourFunc -H "Content-Type: application/json" -d '{}'. You should get a JSON response. - Now make any change to your project — frontend or backend — and redeploy.
- Immediately curl the function again. About 10-20 percent of the time, the function will 404 or 405 even though you did not change it.
- To reliably reproduce: rename your function in the editor and redeploy. Curl the new name. Curl the old name. You will frequently observe both names returning some flavor of 404 or 405 for at least several minutes.
Step-by-step fix
1. Confirm the failure mode with curl
# Test GET — does it return SPA shell?
curl -i https://yourapp.base44.app/functions/yourFunc
# Test POST — does it return 405?
curl -i -X POST https://yourapp.base44.app/functions/yourFunc \
-H "Content-Type: application/json" \
-d '{}'
# Test OPTIONS — does it return 405 or 200?
curl -i -X OPTIONS https://yourapp.base44.app/functions/yourFunc
If GET returns HTML and POST returns 405, you are in the routing desync. If both methods cleanly 404, your function is genuinely unregistered.
2. Force a router refresh by redeploying the function
Open the function in Base44's editor. Add a meaningless whitespace change at the top (e.g., a blank line). Save. Redeploy. The platform treats this as a function update and re-publishes the manifest.
// Add this comment, save, redeploy. Then remove it on the next deploy.
// route-refresh: 2026-05-01
export default async function handler(req: Request) {
// ... your existing code
}
Curl the function immediately after redeploy. The route should now resolve.
3. If the function was renamed, delete the stale name explicitly
In the editor, find the old function name in the function list. Delete it. Save. Redeploy. Then curl both names — the old name should 404 cleanly, the new name should resolve.
4. Verify no path-collision with frontend routes
If your frontend has a route at /functions or /functions/yourFunc (some apps do this for legacy reasons), the router is genuinely ambiguous. Rename the frontend route to /api-functions or similar so the function namespace is unique.
5. Add a routing health check to your post-deploy ritual
Every time you deploy, immediately curl every backend function URL with a small valid POST payload. Script it.
#!/bin/bash
# scripts/check-routes.sh
APP_URL="https://yourapp.base44.app"
FUNCTIONS=("processOrder" "saveProfile" "sendNotification")
for fn in "${FUNCTIONS[@]}"; do
status=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST "$APP_URL/functions/$fn" \
-H "Content-Type: application/json" \
-d '{"healthcheck": true}')
echo "$fn: $status"
if [[ "$status" == "404" || "$status" == "405" ]]; then
echo "ROUTING FAILURE: $fn"
exit 1
fi
done
Run this script as part of your deploy completion check. Fail the deploy if any route does not resolve.
6. Build defensive frontend handlers
When your frontend calls a function and gets back HTML or 405, surface a clear error rather than silently corrupting state.
async function callFunction(name: string, body: object) {
const res = await fetch(`/functions/${name}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
const contentType = res.headers.get("content-type") ?? "";
if (!contentType.includes("application/json")) {
throw new Error(
`Function ${name} routed incorrectly. Got ${res.status} with ${contentType}. ` +
`Likely router desync — redeploy the function.`
);
}
if (!res.ok) throw new Error(`Function ${name} failed: ${res.status}`);
return res.json();
}
This converts a silent corruption into an actionable error in your console.
DIY vs hire decision
DIY this if: You have one or two functions and you can spare an afternoon to build the curl health-check script and add the defensive frontend handlers.
Hire help if: You have more than five backend functions, you have already shipped a routing-desync incident to customers, or you cannot afford another one. Our fix-sprint instruments your entire function fleet with a deploy-time route check, defensive frontend handlers, and a redeploy runbook for when the desync hits anyway. Most teams see zero unrouted-function incidents post-engagement.
Need this fixed in 48 hours?
Our fix-sprint instruments every backend function with a deploy-time route check, ships defensive frontend handlers, documents your redeploy runbook, and verifies zero routing failures over a 72-hour soak. Fixed price.
Start a fix sprint for routing failures
Related problems
- Functions stop working after a few hours — the cold-start sibling of the routing-desync problem.
- 429 rate-limit errors in production — what happens when your retry logic compensates for routing failures.
- Webhooks only fire while a user is active — another platform routing pathology that surfaces in the same area.