Troubleshooting guide

Deploy succeeded but production is broken or out of date

The build passes and the host reports a successful deploy, but production doesn't match what ran on your machine. A stylesheet or JavaScript chunk is missing, serverless functions hang until they time out, an alias serves an old build, or pushes stop deploying. We have 21 verified builder reports of this pattern, across several hosts and frameworks.

21 verified cases9 min read Updated 27 Sep 2026How we collect cases

What you'll see

  • The page loads without its stylesheet, or the console shows 404s for JavaScript chunks and a 'Failed to fetch dynamically imported module' or chunk load error.
  • Some users get intermittent chunk load failures shortly after a deploy, even though the new chunks exist on the server.
  • API routes or serverless functions that answer instantly in local development hang in production and end in a 504 timeout.
  • A branch or production alias keeps serving an older build after a newer deploy reported success.
  • Pushes to the production branch no longer start a deploy, and nothing appears in the host's deployment list.
  • The frontend calls an endpoint or field that the deployed backend does not have yet, or no longer has.

Why it happens

Cause 1

Production is a separate build with its own dependency resolution

The host runs a clean install and build in its own container, with its own Node.js version, package manager and build cache. Without a committed lockfile, a caret range such as ^4.0.0 can resolve to a newer release than the one on your machine. A framework upgrade can also change where the build writes its output. The host uploads whatever is in the configured output directory, even if your assets were written somewhere else.

Cause 2

A stale build cache hides or reintroduces files

Hosts restore the previous build's cache before the install and build commands run. On Vercel the cache key includes the project, framework preset, root directory, Node.js version, package manager and Git branch. Changing any of these, or caching a broken intermediate state, can produce output that differs from a clean local build.

Cause 3

Old pages request chunks that no longer exist

A browser that loaded the HTML before a deploy still references the previous build's hashed chunk names. Once the old assets are removed, that page's next dynamic import fails. The Vite docs describe this case and recommend Cache-Control: no-cache on HTML files so browsers stop referencing old assets.

Cause 4

Functions wait on something they cannot reach

A serverless function times out when it never returns a response, or when an API or database it calls responds slowly or not at all. A missing environment variable, a database that refuses connections from the host's network, and a code path that ends without sending a response all look like a hang from the outside. Pages prerendered at build time can also call live APIs during the build, so the deploy depends on those APIs being reachable at that moment.

Cause 5

Deploy automation was switched off or disconnected

After a Vercel Instant Rollback, auto-assignment of production domains is turned off, so new pushes to the production branch do not replace the rolled-back deployment. Deploys also stop when the repository webhook is gone, the Git connection has lapsed, an Ignored Build Step script exits 0, or git.deploymentEnabled is false. When frontend and backend deploy on separate schedules, they can also go live in the wrong order.

How to fix it

  1. Pin the toolchain and install from the lockfile

    Commit the lockfile and install with npm ci, which fails if package-lock.json and package.json disagree and never rewrites either file. Pin the Node.js version too: Netlify reads .nvmrc or .node-version before falling back to its own default.

    node --version > .nvmrc
    git add package-lock.json .nvmrc
    # locally, in CI and as the host's install command
    npm ci
  2. Reproduce the host's build locally and compare the output

    Delete node_modules and the build output, run npm ci and the same build command the host runs, then serve the output directory with a static server, not the dev server. On Vercel, compare the result with the deployment's Source tab (or /_src on the deployment URL) and the Resources tab, which lists the functions, middleware and assets that were actually built.

  3. Rebuild once without the cache

    If the clean local build is correct and production is not, rule out the cache. On Vercel, redeploy with 'Use existing Build Cache' unchecked, run vercel --force, or set VERCEL_FORCE_NO_BUILD_CACHE=1. On Netlify, deploy the latest branch commit with the clear cache option.

    vercel --force
  4. Make old tabs survive a deploy

    Serve HTML with Cache-Control: no-cache, and reload the page once when a chunk fails to load. On Vercel, Skew Protection pins framework-managed asset and navigation requests to the deployment that served the page. It supports Next.js, SvelteKit, Qwik, Astro and Nuxt on the Pro and Enterprise plans.

    window.addEventListener('vite:preloadError', () => {
      window.location.reload()
    })
  5. Smoke-test the production URL after every deploy

    Fetch the HTML, one real asset it references, and one function or data endpoint, with a timeout. curl -f fails on any HTTP status of 400 or above, so a 404 or 504 fails the script and the release. Adjust the asset pattern to your framework's output path.

    set -e
    URL=https://your-domain.example
    curl -fsS "$URL/" -o /tmp/index.html
    ASSET=$(grep -oE '/assets/[^"]+\.js' /tmp/index.html | head -1)
    curl -fsS "$URL$ASSET" -o /dev/null
    curl -fsS --max-time 10 "$URL/api/health"
  6. Deploy the backend first and keep changes additive

    Ship the API or database change before the frontend that depends on it. Keep old fields and endpoints working until no deployed client uses them. Make every function return a response on every path, including errors, so a failure shows up as a status code instead of a timeout.

  7. If pushes stop deploying, check the automation

    On Vercel, check the production deployment tile for an Undo Rollback button. Use it, or promote a deployment, to turn auto-assignment back on. Then check the repository's webhooks for the host's entry (disconnect and reconnect the repository if it is missing), the Git login connection, the production branch setting, the Ignored Build Step script, and git.deploymentEnabled in vercel.json.

    vercel promote <deployment-url>

Check it's fixed

  • Straight after a deploy, the smoke-test script passes against the production domain, not only against the deployment's own URL.
  • A page left open from before a deploy can still navigate, or reloads once and then works, with no chunk 404s in the console.
  • A commit pushed to the production branch appears in the host's deployment list and becomes the live production deployment without manual promotion.
  • A clean local build (npm ci, then the host's build command) produces the same file list as the deployed output.

Questions

Why does the build pass on the host when production is still broken?

A passing build only proves that the build command exited successfully. It does not check that the output directory holds the files your HTML references, or that functions can reach their database and environment variables at runtime. The post-deploy smoke test checks those.


Why do only some users see chunk load errors?

Only people who loaded the page before the deploy are affected. Their HTML references the previous build's hashed chunks, and once those are removed, their next dynamic import fails. Serve HTML with Cache-Control: no-cache and reload once on vite:preloadError, or use Skew Protection on Vercel.


Is it safe to turn off the build cache permanently?

Yes, but builds get slower. Rebuild once without the cache to confirm or rule it out as the cause. If the cache is the cause, fix the input that differs, such as an unpinned Node.js version or a missing lockfile.


I rolled back on Vercel and now my pushes do nothing. Why?

A rollback turns off auto-assignment of production domains. New deployments still build, but they do not go live. Use Undo Rollback on the production deployment tile, or vercel promote, to turn auto-assignment back on.


Sources

← Back to the full report