Next.js

Fix Vercel 404: NOT_FOUND Deploying an Express App

Why Vercel answers 404: NOT_FOUND for an Express app that runs locally, and the two things to check: the entry file Vercel looks for, and a leftover Build command/Output Directory setting.

Mahdi Ben RhoumaMahdi Ben Rhouma
8 min read

TL;DR#

Vercel's 404: NOT_FOUND on an Express deployment almost always means Vercel found no recognised entry file for your app — no app, index or server file that exports the Express instance or starts a port listener — so there is no function to route requests to. Export your Express app from a file Vercel actually looks for: app.js, index.js or server.js at the project root or under src/. It's also worth checking for a Build command / Output Directory left over from a static-site setup (npm run build / dist), though that setting alone is not what blocks Express from running. If your app is older and still uses a /api/index.js layout, add a rewrites rule so the API isn't stuck under /api.

text
404: NOT_FOUND
Click here to learn more about this error.

Why it happens#

This is the exact failure in a widely-viewed Stack Overflow question, How to deploy ExpressJS Node-application to Vercel. Getting 404: NOT_FOUND error (20 votes, close to 15,000 views). The asker had a working TypeScript + Express app, running locally through Nodemon, and deployed it to Vercel with:

  • Build command: npm run build
  • Output Directory: dist
  • Install command: npm install

Vercel's own troubleshooting guide for this exact error says what to look at first:

"If the project uses a traditionally server-side framework, such as Express, you should make sure to put the index.js file in a /api directory so it will be deployed as a serverless function. In that case the path to the first page of your site would look like my-website.com/api which can be altered to use the root path / using rewrites." — How to debug 404 errors

In other words, the primary thing Vercel needs is a file it recognises as the Express entry point. Vercel's current guide to deploying Express confirms this is handled automatically once that file exists, with no build settings needed:

"Because Vercel ships zero-configuration detection for Express, you don't set a build command or output directory. Vercel reads your project, finds the file that exports your Express app (or starts it with a port listener), and applies the correct build settings." — How to ship an Express app on Vercel

The Build command and Output Directory are a secondary thing worth checking, not the mechanism that stops Express from running — Vercel's own docs on this error describe the Output Directory as "often overlooked" rather than as something that blocks function execution:

"The Output Directory is often overlooked, but can have a big effect on the deployment. Only the contents of this directory are statically served as part of your deployment, so it's important that the correct value is used for this setting." — How to debug 404 errors

This matches what actually happened in the thread: the asker added a root-level index.js and a vercel.json, and the app started working — while leaving the Build command (npm run build) and Output Directory (dist) exactly as they were. In their own words: "my Vercel-build-settings (listed higher up) was still the same. Weird!" It was not weird: once Vercel found a recognised entry file, it ran the Express function regardless of that leftover setting.

Two of the thread's own answers land on the same fix from different years: one (score 4) adds a root-level index.js that does require("./bin/www") plus a legacy vercel.json builds/routes block pointing at it; the other (score 2) uses the newer Standalone Express pattern — an index.js under /api and a rewrites entry in vercel.json so /api/(.*) serves everything. Both work for the same reason: they put a file Vercel's build step recognises as a function where the platform expects to find one, instead of asking Vercel to serve a compiled dist/ folder as static files.

Fix#

  1. Remove the Build command and Output Directory from your Vercel project settings if your project is a standard Express app (no separate static build step). Vercel reads your project, finds the file that exports your Express app, and applies the correct build settings on its own; manually set values only override that detection and can point it at the wrong place.

  2. Put your Express app in a file Vercel's build step recognises. The current guide's detection looks for app, index or server (.js, .ts, or a related extension) at the project root or under src/:

    typescript
    import express from 'express';
     
    const app = express();
     
    app.get('/', (req, res) => {
      res.json({ message: 'Hello from Express on Vercel' });
    });
     
    export default app;

    If your package.json has "type": "commonjs", export with module.exports = app instead of export default app — the guide names this explicitly as the CommonJS form. Starting the app with app.listen(...) instead of exporting it also works; Vercel detects either pattern.

  3. If you are keeping the older /api/index.js layout (the pattern in the Stack Overflow thread's second answer), add a rewrite so visitors don't have to go through /api for every route:

    json
    {
      "rewrites": [{ "source": "/(.*)", "destination": "/api" }]
    }
  4. Do not rely on express.static() for assets on Vercel. The Express guide is explicit that it is ignored on the platform; put static files such as images or a favicon in public/** instead, and Vercel's CDN serves them.

  5. Test the build locally before redeploying. The debug guide for this error points at the Vercel CLI's vercel build command specifically so you can check configuration changes without a new deployment for each one. Vercel's dashboard also lets you inspect the Source and Output tabs of a deployment to see what actually shipped.

Test the build locally before redeploying#

Rather than guessing and pushing a new deployment for every change, the official debug guide for this exact error points at the Vercel CLI's own build step:

"you can use the CLI to test a build on your local computer with the vercel build command. That can help you test configuration changes without creating a new deployment for each change." — How to debug 404 errors

bash
npm i -g vercel
vercel link      # connect this folder to your Vercel project once
vercel build     # builds locally, the same way Vercel would on deploy

Run this after step 1 (settings removed) and step 2 (entry file in place). If vercel build finishes with your Express function detected rather than a static dist/ output, the same build will succeed when you push it — you have already seen what the platform will do with your project, instead of finding out from a 404: NOT_FOUND screen on a live deployment.

Verify the fix#

Deploy again (vercel --prod from the CLI, or push to the branch your Git integration watches) and open the deployment's Source tab. You should see your Express entry file picked up as a function, not a static dist/ folder with no handler for /. Requesting the root path should now return what your route handler sends, not the 404: NOT_FOUND screen — and the "Code" and "ID" that screen used to show are gone because the request matched a function instead of falling through.

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

If the next error is CORS, a missing build, or middleware that silently stops running#

Once the Express API itself answers, a separate frontend calling it from the browser often hits a different error next: the browser blocks the response with "has been blocked by CORS policy" because the deployed API has no Access-Control-Allow-Origin header for that origin. That is a distinct problem from the 404 above — see Fix: blocked by CORS policy from origin localhost:3000 for the three concrete causes and fixes.

If instead your Vercel deployment fails before it even reaches this routing problem — rejected at the Git step — the cause is usually unrelated to Express at all: see Fix Vercel 'Git author must have access to the project' for the three access rules Vercel checks on a private repository.

If instead your assets are 404ing in a Next.js project rather than an Express one — a file in public/logo.png answering 404 at that same path — the cause is a different URL mapping: Next.js serves everything in public/ from the site root, so the fix is to drop the /public prefix from the URL, not to touch Express or express.static(); see Next.js Images Not Loading from public/static Folder: Fix for that mismatch specifically. The same build/output confusion shows up from the other direction in Next.js projects, where next start cannot find the production build it expects — see Fix Next.js missing .next/BUILD_ID at server startup for that failure mode specifically.

A deployment that builds and serves pages, but where one specific piece of server-side logic silently stops firing in Vercel's production environment while it worked locally, is often not a 404 at all but a different platform/runtime mismatch — Next.js Middleware Not Running on Vercel: 3 Causes covers that failure mode for Next.js's own middleware layer.

A 404 that only shows up on one route, rather than every route, usually has a narrower cause: a rewrite that matches too little, or a route defined after the catch-all handler in your Express router. Check the order your routes are registered in — Express matches top to bottom, so a wildcard route declared early silently shadows everything after it, on Vercel exactly as it would running locally.

Further reading: CORS Not Working on Vercel? Fix the vercel.json Headers.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote