Fix Vercel “MIDDLEWARE_INVOCATION_FAILED” errors
Explore with AI
MIDDLEWARE_INVOCATION_FAILED Vercel returns this 500 Internal Server Error when your Routing Middleware, such as a Next.js middleware.ts or proxy.ts, fails while handling a request, usually through an unhandled exception. Find the failing request in runtime logs filtered to middleware, catch the error so the middleware still returns a response, narrow the matcher, and move edge middleware to the Node.js runtime if it uses Node APIs or heavy libraries.
On this page
- What MIDDLEWARE_INVOCATION_FAILED means
- Finding the failing middleware request in Vercel logs
- Cause 1: an unhandled exception in the middleware
- Cause 2: Node.js APIs or eval under the Edge runtime
- Cause 3: a module that won’t load or a bundle over the edge size limit
- Cause 4: no response started within 25 seconds
- When the code is EDGE_FUNCTION_INVOCATION_FAILED
- How to confirm the middleware is fixed
- Keeping middleware from taking the site down
- Tracing MIDDLEWARE_INVOCATION_FAILED back to a change with Polylane
- Common questions
This one tends to look worse than a normal 500. Middleware runs before your routes, so when it fails, every page its matcher covers fails with it. Your home page, your login page and sometimes your static assets all return the same Vercel error page, even though none of their own code changed.
The code tells you where the failure happened: in the middleware step, before your app got the request. It doesn’t tell you why. The reason is in your runtime logs, and it’s usually one of a short list: an exception your middleware didn’t catch, an API the Edge runtime doesn’t have, a bundle that’s too big, or a slow call that kept the response from starting.
What MIDDLEWARE_INVOCATION_FAILED means
Vercel’s error reference says the code appears when there’s an issue with the Routing Middleware being invoked on the CDN, caused by things such as unhandled exceptions. The status is 500 and the name is Internal Server Error. Vercel classes it as an application error.
Routing Middleware is the code in a Next.js middleware.ts or proxy.ts, or the entrypoint you set with the proxy property in vercel.json for other frameworks. The Routing Middleware docs say it executes before a request is processed on a site, and runs globally before the cache. That position is why a failure spreads so far. A request that would have been a cache hit still runs the middleware first, so a crash there turns cached pages into 500s too.
A close relative is EDGE_FUNCTION_INVOCATION_FAILED. It is the same kind of failure in a function that uses the Edge runtime. The section on it is further down.
Finding the failing middleware request in Vercel logs
Middleware invocations appear in runtime logs alongside your functions. Open the project, go to Logs, set the Resource filter to Routing Middleware, and set Status Code to 500. In the request details, the Middleware section shows where it ran and for how long, and Log Messages holds anything your middleware printed, including the error. Vercel’s error reference also points to the /_logs path on your deployment’s host URL.
From the CLI, filter by the middleware source:
# Middleware errors in production over the last hour
vercel logs --source edge-middleware --environment production --status-code 500 --since 1h --expand
# One request, every line
vercel logs --request-id <request-id> --expand
The runtime logs docs keep logs for 1 hour on Hobby, 1 day on Pro and 3 days on Enterprise, so pull them while the failures are recent.
Two quick checks narrow the cause before you read any code:
- Which paths fail? If every path fails, including ones your middleware does nothing with, the crash is at module load or near the top of the function. If only some fail, the bug is in a branch that only those paths reach.
- When did it start? Compare the first failing request with your deployment list. A new deployment points at your code. No deployment points at something external the middleware depends on, such as an auth provider or a config API.
When we built observability for Workers at Cloudflare, middleware-style code at the front of every request was where one small error caused the widest outage. Logging each failure with the request path makes these much faster to solve.
Cause 1: an unhandled exception in the middleware
How to tell it’s yours: the middleware log shows a stack trace from your own code. Common ones are a JWT library throwing on a malformed or expired token, JSON.parse failing on a cookie, new URL() failing on a header value, or a fetch to an auth or feature-flag service rejecting.
This is the cause Vercel names in the error reference, and the one people hit most. Middleware reads untrusted input on every request: cookies, headers and query strings. One request with an odd value can reach a code path that throws.
Fix:
- Find the line from the stack trace and fix the bug itself, for example by checking a cookie exists before you parse it.
- Wrap the middleware body in
try/catchso a throw never escapes, and decide what a failure means for each path. Protected paths fail closed: no verified session, or any error while checking it, sends the user to log in. Every other path continues withNextResponse.next(), which keeps the public site up. Verify the session itself, because a cookie that is merely present can be forged or expired. This example checks a signed JWT withjose; use your auth library’s own check if it has one:
Both route families go through the same// proxy.ts (middleware.ts before Next.js 16) import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; import { jwtVerify } from "jose"; const PROTECTED = ["/dashboard", "/account"]; const secret = new TextEncoder().encode(process.env.SESSION_SECRET); const isProtected = (path: string) => PROTECTED.some((p) => path === p || path.startsWith(`${p}/`)); export async function proxy(request: NextRequest) { const { pathname } = request.nextUrl; if (!isProtected(pathname)) return NextResponse.next(); try { const token = request.cookies.get("session")?.value; if (!token) throw new Error("no session cookie"); await jwtVerify(token, secret); // throws on a forged, malformed or expired token return NextResponse.next(); } catch (err) { console.error("proxy auth failed", pathname, err); return NextResponse.redirect(new URL("/login", request.url)); } } export const config = { matcher: ["/dashboard/:path*", "/account/:path*"], };isProtectedcheck, so/accountgets the same treatment as/dashboard. If you widen the matcher later, public paths such as/and/loginstill fall through toNextResponse.next(), so there is no redirect loop. - Keep authorization in your routes too. The Next.js proxy docs also advise checking authentication inside each Server Function, so a middleware failure or a matcher change doesn’t leave data open.
- Log the path with the error. When a crash only happens for some requests, the path and the failing value are what you need to reproduce it.
Narrow the matcher so fewer requests run middleware
The Next.js docs warn that without a matcher, middleware runs on every request, including _next/static, _next/image and files in public/. A crash then breaks CSS, JavaScript and images as well as pages. Match only what the middleware needs:
export const config = {
matcher: [
"/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)",
],
};
Matcher values must be constants, because Next.js reads them at build time. Dynamic values such as variables are ignored. The docs also note that middleware still runs for _next/data routes even if you exclude them, which is deliberate.
For other frameworks, Vercel’s docs say you can add proxy.matcher in vercel.json to limit which paths run the Routing Middleware. If your logic is only redirects, rewrites or headers, the same docs suggest static rules in vercel.json, which don’t invoke your code at all.
Cause 2: Node.js APIs or eval under the Edge runtime
How to tell it’s yours: the middleware runs on the Edge runtime, and the log shows an error about a missing Node.js module, an undefined require, filesystem access, or code generation from strings such as eval. These often come from a library your middleware imports.
The Edge runtime docs list the restrictions. Only some Node.js APIs are supported, and you can’t read or write the filesystem. Calling require directly isn’t allowed. node_modules work only if they use ES modules and don’t use native Node.js APIs. eval, new Function(evalString) and dynamic WebAssembly.compile and WebAssembly.instantiate are disabled, and the docs say dynamic code execution in edge middleware leads to a runtime error.
Fix: move the middleware to the Node.js runtime. Vercel’s Edge runtime page now recommends migrating from edge to Node.js for better performance and reliability. It also says that starting in Next.js 16.3, setting runtime = 'edge' is no longer supported.
- On Next.js 16 or later, rename the file to
proxy.ts. The codemod renames the file and the exported function:
Proxy defaults to the Node.js runtime. Remove anynpx @next/codemod@canary middleware-to-proxy .runtimeoption from the config, because the Next.js docs say setting it in a proxy file throws an error. - On Next.js 15.5, where Node.js middleware is stable, set the runtime in
middleware.ts:export const config = { runtime: "nodejs", matcher: ["/dashboard/:path*"], }; - Outside Next.js, Vercel’s Routing Middleware docs say the default runtime for the
middleware.tsconvention is Node.js, and an entrypoint set through theproxyproperty invercel.jsonruns on Node.js. Check that noruntime: 'edge'is left in your config. - Redeploy and check the build output to confirm the middleware no longer builds for edge.
Cause 3: a module that won’t load or a bundle over the edge size limit
How to tell it’s yours: the build succeeds, but the middleware fails on every request with a module resolution error, or the deploy fails with a size error for the middleware. It often starts after you add an SDK or a large dependency to the middleware’s imports.
Edge middleware is bundled into a single unit with strict size limits. The Edge runtime docs set the limit after gzip compression at 1 MB on Hobby, 2 MB on Pro and 4 MB on Enterprise. That counts your code, imported libraries and any files bundled in, such as fonts. A library that is fine in a Node.js function can be too large, or can rely on Node APIs and fail to load, under edge.
Fix:
- List what the middleware imports. Every import pulls its whole dependency tree into the middleware bundle, including shared helpers that import your database client or a full SDK.
- Import only what the middleware needs. Move heavy work, such as database lookups, into a route handler or a page, and pass results through headers, cookies or rewrites, as the Next.js docs suggest.
- Check package sizes before you add them. The Edge runtime docs suggest a package size checker such as bundle to find a smaller alternative.
- If the middleware really needs the library, move it to the Node.js runtime as in Cause 2. The edge code size limit then no longer applies.
- Run
next buildlocally to reproduce. If it builds locally and fails only on Vercel, compare the Next.js version in your lockfile with the one in the failing deployment’s build logs.
Cause 4: no response started within 25 seconds
How to tell it’s yours: the failing requests are slow before they fail. The Middleware section of the request details shows a long duration, and the middleware awaits a call to another service.
The Edge runtime docs say middleware with the edge runtime must begin sending a response within 25 seconds. It can keep streaming after that, and can continue asynchronous work in the background after it returns. Middleware that awaits a slow auth service, database or config API on every request can hit that limit when the dependency slows down.
Fix:
- Put a timeout on every external call the middleware makes, well under 25 seconds:
const res = await fetch("https://auth.example.com/verify", { headers: { authorization: token }, signal: AbortSignal.timeout(2000), }); - Treat a timeout like any other failure in the
try/catchfrom Cause 1: let public paths through and send protected paths to log in. - Move work that doesn’t decide the response out of the critical path. Next.js gives middleware
event.waitUntil(promise), which keeps the invocation alive until the promise settles, so logging or analytics can finish after the response is sent. - If the middleware needs data from far away, the Routing Middleware docs suggest a global store such as Global Config, to avoid slow cross-region calls.
For timeouts in your functions after the middleware has run, see how to debug Vercel function timeouts.
When the code is EDGE_FUNCTION_INVOCATION_FAILED
Vercel returns EDGE_FUNCTION_INVOCATION_FAILED when a function that uses the Edge runtime fails. The error reference gives the same 500 Internal Server Error and lists unhandled exceptions, timeouts and malformed requests as causes. It asks you to check the logs at /_logs, review the deployment configuration, look for build errors, and check the code for errors and infinite loops.
Everything above applies. The usual culprit is a route handler or API route with runtime: 'edge' in its config, hitting the same restrictions as edge middleware: missing Node APIs, disabled eval, the 1 MB to 4 MB code size limit, and the 25 second limit to start a response.
The lasting fix is the same too. Remove the edge runtime from the route so it runs on Node.js:
// app/api/search/route.ts
// Delete this line:
// export const runtime = "edge";
export async function GET(request: Request) {
// ...
}
On Next.js 16.3 or later, Vercel’s docs say runtime = 'edge' is no longer supported and routes run on Node.js. If the crash continues after the move, it’s now a Node.js function error, so follow the FUNCTION_INVOCATION_FAILED guide.
How to confirm the middleware is fixed
- Request a few paths the matcher covers, including one protected path and one public path:
Public paths should returnfor p in / /dashboard /account; do curl -s -o /dev/null -w "$p %{http_code}\n" "https://your-app.vercel.app$p" done200. Protected paths should return your login redirect when you’re signed out. - Request a static asset the matcher now excludes, such as
/favicon.ico, and check it returns200. - Watch for new middleware errors on the new deployment:
No new rows withvercel logs --source edge-middleware --status-code 5xx --since 30m --expandMIDDLEWARE_INVOCATION_FAILEDorEDGE_FUNCTION_INVOCATION_FAILEDmeans the fix holds. - Open Observability and the Middleware tab. It shows invocation counts for your middleware, so you can also check that the narrower matcher cut the number of invocations.
If the Vercel status page reports a CDN incident while you see this code and nothing in your logs explains it, the error reference lists that as a possible cause too.
Keeping middleware from taking the site down
- Keep middleware small. The Next.js docs call middleware a last resort. Do auth checks, redirects and rewrites there, and nothing else.
- Use static rules where you can. Redirects, rewrites and headers that don’t need code belong in
vercel.jsonor project routing rules. - Always catch. Every middleware should have a top-level
try/catchwith a decided fail-open or fail-closed path. - Time out every call. No external call in middleware should be able to run for long.
- Check the bundle in review. Flag any new import in
middleware.tsorproxy.ts, since each one runs on every matched request. - Alert on middleware 5xx. A spike in middleware errors affects every matched route, so treat it as a site-wide incident.
Tracing MIDDLEWARE_INVOCATION_FAILED back to a change with Polylane
Polylane connects to Vercel with OAuth and can query its logs and metrics, and alerts that fire on a connected Vercel account become issues. A fix run reads the middleware logs, recent deploys and your code, traces the failure to its cause with evidence, and opens a pull request for you to review when a code change fixes it.
Running on Vercel? See how Polylane monitors Vercel in production.
Common questions.
What status code does MIDDLEWARE_INVOCATION_FAILED return?
It returns 500, named Internal Server Error in Vercel's error reference. Vercel describes it as an issue with the Routing Middleware being invoked on the CDN, caused by things such as unhandled exceptions. Because middleware runs before your pages, every route the matcher covers can return it at once.
What is the difference between MIDDLEWARE_INVOCATION_FAILED and EDGE_FUNCTION_INVOCATION_FAILED?
Both are 500 errors. MIDDLEWARE_INVOCATION_FAILED comes from Routing Middleware, which runs before a request reaches your routes. EDGE_FUNCTION_INVOCATION_FAILED comes from a function that uses the Edge runtime, and Vercel lists unhandled exceptions, timeouts and malformed requests as causes.
Does Vercel still recommend the Edge runtime for middleware?
No. Vercel's Edge runtime page recommends migrating from edge to Node.js for better performance and reliability, and says both run on Fluid compute. It also says that starting in Next.js 16.3, setting runtime = 'edge' is no longer supported and routes run on Node.js. Vercel's Routing Middleware docs list Node.js and Bun as the available runtimes.
Can I use fs, require or eval in middleware?
Not under the Edge runtime. Vercel's docs say you can't read or write the filesystem, calling require directly isn't allowed, and eval and new Function(evalString) are disabled. Switch the middleware to the Node.js runtime, or replace the library that needs them.
How long can middleware take before it fails?
Middleware with the edge runtime must begin sending a response within 25 seconds. It can keep streaming after that, and work in the background after it returns. Any external call in middleware should have its own timeout well under that limit.
How big can edge middleware be?
The Edge runtime code size limit after gzip is 1 MB on Hobby, 2 MB on Pro and 4 MB on Enterprise. The limit counts your code, imported libraries and bundled files such as fonts. Moving to the Node.js runtime removes this edge-specific limit.
Should I rename middleware.ts to proxy.ts?
On Next.js 16 and later, yes. The middleware file convention is deprecated and renamed to proxy, and npx @next/codemod@canary middleware-to-proxy . renames the file and function for you. Proxy defaults to the Node.js runtime, and setting the runtime option in a proxy file throws an error.
Can I see middleware logs from the CLI?
Yes. Run vercel logs --source edge-middleware --level error to see middleware errors, and add --status-code 500 and --expand to read the full messages. The dashboard's Logs page has a Resource filter for Routing Middleware that does the same.
Sources
- MIDDLEWARE_INVOCATION_FAILED (Vercel Docs)
- EDGE_FUNCTION_INVOCATION_FAILED (Vercel Docs)
- Edge Runtime (Vercel Docs)
- Routing Middleware (Vercel Docs)
- Runtime Logs (Vercel Docs)
- vercel logs (Vercel CLI Docs)
- Observability Insights (Vercel Docs)
- proxy.js file convention (Next.js Docs)
- middleware.js file convention (Next.js Docs)
- Polylane documentation
- Polylane full content
Boris Tane is the founder of Polylane. He previously founded Baselime, observability for the future of the cloud, which Cloudflare acquired. At Cloudflare he built and led the Workers observability team.
Related
- Fix Vercel “FUNCTION_INVOCATION_FAILED” 500 errors
Why a Vercel Function returns 500 FUNCTION_INVOCATION_FAILED, how to find the crash in runtime logs, and how to fix throws, missing env vars, files and memory.
- How to debug Vercel function timeouts
Debug Vercel FUNCTION_INVOCATION_TIMEOUT 504s: find the slow call with vercel logs and httpstat, fix hangs, set maxDuration and verify on a preview.
- Vercel 504 Gateway Timeout on Serverless Functions: Causes and Fixes
Fix Vercel 504 FUNCTION_INVOCATION_TIMEOUT errors: current duration limits, how to find the slow call, the fix for each cause and how to raise maxDuration.
- Fix Cloudflare “Error 1102: Worker exceeded resource limits”
Why a Cloudflare Worker returns Error 1102, how to tell a CPU time overrun from a 128 MB memory overrun, and the code and Wrangler changes that fix each.
- How to attach Vercel Sandbox to a Secure Compute network
Vercel Sandbox now supports Secure Compute. Attach a sandbox to your network for static egress IPs and VPC peering, with steps, costs and gotchas.