[▲ Vercel Community](/) · [Categories](/categories) · [Latest](/latest) · [Top](/top) · [Live](/live) [Help](/c/help/9) # Vercel Edge Function BFF returns intermittent NOT_FOUND errors on API routes 3 views · 0 likes · 3 posts Acud (@acud) · 2026-04-03 Hello there, I’m developing an application that has a `Vercel` frontend deployment and a `Node.js` backend running on `Hetzner`. ## Problem During the initial design iterations, I’ve made the decision to try to harden the backend access by using a `BFF` (Backend-for-Frontend) that runs on `Vercel`. The idea is that the requests that the frontend needs to do to the backend would be done to a function that would inject an extra `API_KEY` that basically eliminates all other access to the backend unless it is made from that `BFF`. The idea was to have `DDoS` protection and rate limiting out of the box using `Vercel`’s proxying infrastructure (I’m not 100% the assumption would hold for functions, but I’m pretty sure it would hold for the regular frontend parts), instead of complicating things with extra services like `Cloudflare` etc. Generally, it works well, but occasionally I get `Vercel` `NOT_FOUND` errors which aren’t very clear on where they are coming from. I’ve managed to get rid of them for a while by disabling caching altogether on those routes, but today they’ve reappeared and this is last ditch effort to find a resolution before completely removing this from the codebase. ## Current Behavior - When the `NOT_FOUND` error is shown, no actual request makes it to the backend. - There’s no actual request to the `BFF` function either. - I also cannot find the request ID that I see on the error in the logs. - In short, there is zero information to work with. I tried contacting `Vercel` support at some point about this but they’ve rolled over the responsibility to me and did not help me or figure it out. ## Implementation Details The `Vercel` function definition which sits in the `/api/[…path].ts` file: ```tsx export const config = { runtime: 'edge' } export default async function handler(req: Request) { try { const url = new URL(req.url); const backendUrl = process.env.BACKEND_URL || 'http://localhost:3000'; // Extract path from URL (remove /api prefix) const pathString = url.pathname.replace(/^\/api\//, ''); const targetUrl = `${backendUrl}/api/${pathString}${url.search}`; console.log( `[BFF] Incoming request: ${req.method} /${pathString}${url.search}` ); console.log(`[BFF] Target URL: ${targetUrl}`); console.log(`[BFF] Request headers:`, { contentType: req.headers.get('content-type'), hasAuth: !!req.headers.get('authorization'), hasCookie: !!req.headers.get('cookie'), }); const headers = new Headers({ 'Content-Type': req.headers.get('content-type') || 'application/json', }); // Inject API key for backend protection if (process.env.API_KEY) { headers.set('X-API-Key', process.env.API_KEY); console.log(`[BFF] Injecting API key header`); } else { console.log(`[BFF] No API_KEY configured`); } // Forward auth headers from client const authHeader = req.headers.get('authorization'); if (authHeader) { headers.set('Authorization', authHeader); console.log(`[BFF] Forwarding authorization header`); } // Forward cookies from client const cookieHeader = req.headers.get('cookie'); if (cookieHeader) { headers.set('Cookie', cookieHeader); console.log(`[BFF] Forwarding cookies`); } console.log(`[BFF] Sending request to backend...`); let body: BodyInit | undefined; if (['POST', 'PUT', 'PATCH'].includes(req.method)) { body = await req.text(); } const response = await fetch(targetUrl, { method: req.method, headers, body, redirect: 'manual', }); ``` `tsconfig.json`: ```json { "compilerOptions": { "target": "ES2020", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, "types": ["vite/client"], "paths": { "@/*": ["./src/*"] } }, "include": ["src", "vite-env.d.ts"], "references": [{ "path": "./tsconfig.node.json" }] } ``` `tsconfig.node.json` ```json { "compilerOptions": { "composite": true, "skipLibCheck": true, "module": "ESNext", "moduleResolution": "bundler", "allowSyntheticDefaultImports": true, "strict": true }, "include": ["vite.config.ts", "vitest.config.ts"] } ``` `vercel.json`: ```json { "buildCommand": "cd ../.. && pnpm build --filter=web", "outputDirectory": "dist", "installCommand": "cd ../.. && pnpm install", "framework": "vite", "headers": [ { "source": "/api/(.*)", "headers": [ { "key": "Cache-Control", "value": "no-store, no-cache, must-revalidate, max-age=0" }, { "key": "CDN-Cache-Control", "value": "no-store" }, { "key": "Vercel-CDN-Cache-Control", "value": "no-store" } ] } ], "rewrites": [ { "source": "/((?!api/).*)", "destination": "/index.html" } ] } ``` `vite.config.ts`: ```tsx import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import path from 'path'; export default defineConfig({ plugins: [react()], resolve: { alias: { '@': path.resolve(__dirname, './src'), }, }, server: { port: 5173, proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true, }, }, }, build: { sourcemap: true, }, }); ``` I would greatly appreciate any input if something is very obvious to anyone regarding this. It really appears to be a configuration problem on my end but honestly it is not very obvious on how to get this to work reliably. Many thanks! Jacob Paris (@jacobparis) · 2026-04-03 Where are you seeing the NOT FOUND errors? Can you share the one of the request IDs that doesn't exist in your project and I'll see if I can track down what project it's supposed to be related to? Double check though that it's not just a request that's older than your log visibility, as that'd be the obvious reason it doesn't show up About the architecture: you seem to be using your BFF layer just as a proxy, so if one of your pages needs to make three calls to your backend, that's three calls coming from your frontend, through the BFF, to your backend, and then back A big part of the value of the BFF is that you can make a custom "endpoint" in your app to perfectly serve a page's requirements, so it's only one hop from the frontend to the BFF, then a few back and forth to the backend, and then one hop back to the frontend with everything it needs. You want those "back and forth" hops to be as short as possible, so ideally the BFF is deployed to the same datacenter as your backend (or as close as possible) The edge runtime positions the BFF closer to the user instead, so it actually hurts performance in this case as the multiple round trips will be happening over a longer geographical distance. If you prefer to continue doing a straight proxy approach (which does give you the DDoS protection, observability, etc) we're actually doing that with this forum here. You can make a new Vercel project for your hetzner backend and deploy a vercel.ts file that rewrites all traffic to your hetzner URL. On the hetzner side, you verify that the proxy header is included and then you know there can't be any direct access outside of the vercel proxy. https://vercel.com/blog/how-we-run-vercels-cdn-in-front-of-discourse Acud (@acud) · 2026-04-04 Hi @jacobparis . Thanks for your response. Here’s an identifier of one of the errors: `fra1::6cwcg-1775199256422-25c889bae8fc`. To answer your first question, the error is seen in the browser when the browser does a call to the backend through the function to start and OAuth flow with Google. The observed behavior is that most of the time it works, then at some point it just packs up and the error starts showing, until after some time it resolves again. There’s nothing concrete I can point to that causes this behavior. It isn’t a function cold start either. It just errors fast and immediately, no delay or anything, which is quite strange. I’ve never been able to find it in the logs. Not immediately after and not some time after. Thank you for clarifying the deviation from the BFF proxy. What you’re saying does make a lot of sense, and I’ll have to revisit this design at a later stage. For now, the relevant functionality has been removed so that I could just unblock and continue working by calling the backend directly. The problem mentioned above, though, still puzzles me. Since the errors aren’t traceable and those request IDs don’t show up in the project’s logs, I would be quite averse to using Vercel functions in general as this seems to create significant unknowns in the process of debugging things.