← All case studies
RESCUEdeploy

Replit Deployment 404: When Your App Works in the Workspace and Dies in Production

2026-08-15 · RESCUE

The second archetypal failure of vibe-coded apps, after broken auth: the preview that lies. The tool shows you a working app — buttons, data, everything — and you assume the public URL works the same way. Then you send the link to a customer and it 404s.

This teardown documents the pattern we see across Replit apps: the app runs in the Workspace (the editing view), but the production Deployment (the public link) returns a blank page, a 404, or "Something went wrong." No client names, no invented results — this is the failure mode, how to recognize it, and how it gets fixed.


The Platform: Replit

Replit gives you an all-in-one environment: an editor, a Workspace where your app runs while you build it, and a separate Deployment system that publishes a public URL. It can scaffold a Node, Python, or Next.js app in one prompt, and the "Run" button starts your server instantly.

The trap is that these are two different environments with the same name. The Workspace runs your code through a special preview proxy. The Deployment builds and serves your code under a different set of rules. Vibe-coding prompts rarely mention the difference, so nothing reconciles the two — and the cracks appear exactly where the environments differ.


What Failed: Works in the Workspace, 404 in Production

Four environment differences cause almost every "works in preview, dies in production" Replit report. Most apps we diagnose have two or more at once.

1. The Deployment was created as Static, but the app is a server

Replit's Deployment screen offers two types. Static serves only files that physically exist in your project. Autoscale actually runs your server process.

If your app has any server logic — an Express or Flask backend, Next.js API routes, a login system — and the Deployment was created as Static, there is no server running in production. The homepage might load if there happens to be an index.html, and everything else 404s. In the Workspace it all works, because the preview proxy runs your server. In production, nothing does.

2. Secrets exist in the Workspace but not in the Deployment

Replit keeps two separate lists of secret values (things like API keys): one for the Workspace, one for each Deployment. They do not sync.

A vibe-coded app reads process.env.OPENAI_API_KEY or os.environ["OPENAI_API_KEY"]. In the Workspace, the secret is there and everything works. In the Deployment, the same variable comes back undefined — so the AI call, the payment call, or the database connection fails, and the app either crashes or silently returns garbage. Worse, secrets added after the Deployment was created don't reach it until you redeploy, which makes the problem look intermittent.

3. The run command drifted between the two environments

The .replit file controls the Workspace "Run" button:

# .replit — this only controls the Workspace preview
run = "npm run dev"

The Deployment has its own Build and Run commands, configured in its settings panel. If the app was only ever started with the development server (npm run dev, flask run), the Deployment's default guess — a build step, or nothing — either fails silently or serves a stale, broken version. The Workspace is showing you the dev server; production is running something else entirely.

4. Nobody ever visited the cold URL from outside Replit

Free-tier Autoscale Deployments sleep after a few minutes without traffic. The first visitor pays a 10–30 second cold start. If you always demo the app from inside Replit — where the preview is warm — you never notice. The customer's browser times out, and everyone concludes "the site is down" when the real story is "the site was asleep."

There is a final trap that makes all of this worse: testing from inside Replit doesn't test the Deployment. Running curl in the Replit shell hits the Workspace, not the public URL. Every check below has to happen from outside — your phone, an incognito window, or another network.


Diagnosis: How to Tell If Your App Has This

You don't need code access. Five checks, all from outside Replit:

1. Open the Deployment URL on your phone, in incognito mode

Note exactly what you get. A 404 on every page that isn't a plain file → no server running (Static trap). A blank page or 500 → the server started but crashed (usually a missing secret). A slow first load that works on refresh → cold start, not a bug in your code.

2. Check the Deployment type

Deployments → your deployment → Settings. If it says Static and your app has a login, an API, or any backend at all: that's the bug, or at least one of them.

3. Compare the two secret lists

Tools → Secrets (the Workspace list) vs. Deployment → Settings → Secrets (the Deployment list). Every key your code reads must exist in both. Any that only exist in one place is a production-only crash waiting.

4. Read the Deployment build logs

Deployments → your deployment → Logs. A Workspace that runs fine tells you nothing about whether the Deployment's build step succeeded. Look for "Build failed", missing dependencies, or a run command that never starts your actual server.

5. Hit a backend route directly

Type an API endpoint into the browser — /api/health, or whatever route your login form calls. If the homepage loads but every dynamic route 404s, the Static trap is confirmed: the file server is up, the server process is not.


The Fix

The repair is unglamorous but decisive: make the two environments actually match.

Layer 1 — Recreate the Deployment with the right type

A Static deployment cannot be converted. Delete it, create a new one as Autoscale, and point it at the same project. This alone fixes the "homepage loads, everything else 404s" symptom — because now there is a real server process running in production.

Layer 2 — Reconcile the secrets

Copy every secret from the Workspace list into the Deployment's own secret list, then redeploy. Make it a rule: whenever you add a secret in one place, add it in both. This is not a one-time fix; it's a workflow, because the drift comes back every time a new key is added.

Layer 3 — Set explicit Build and Run commands

In the Deployment settings, set the commands explicitly instead of accepting defaults:

// package.json — the scripts the Deployment must use
{
  "scripts": {
    "build": "npm run build",
    "start": "node server.js"
  }
}

The Deployment's Run command should start the production server — npm start, python main.py, gunicorn app:appnot the dev server. If the build step needs environment variables (it usually does), those must exist in the Deployment's secrets before the build runs.

Layer 4 — Handle the cold start

If this URL is production, enable Always On on the Deployment so it never sleeps. If that's not possible on the plan, set a simple uptime monitor (a free one is fine) that pings the URL every few minutes — this keeps the app warm and, more importantly, tells you the moment it isn't.

Layer 5 — Verify from outside, once, properly

The fix is not verified until it's tested from a device that never touched the Workspace: incognito phone browser on mobile data, and a curl from a different network against the public URL. Inside Replit, everything looks fixed by definition — which is how the bug shipped in the first place.


Cost: What This Repair Looks Like at RESCUE Prices

Phase What's delivered Price
Diagnosis Written report: which of the four environment differences apply to your Deployment, with the exact fix for each. Delivered in 72 h from code access. €149
Emergency Fix One bug scope: the public URL works — correct Deployment type, explicit Build/Run commands, secrets reconciled, cold start handled. Delivered as a pull request (a proposed change you approve before it applies) + 2-minute screen recording. 5 working days. €590
Launch Pass Emergency Fix scope + launch hardening: a CI check that builds the app before every change so the Workspace can never drift ahead of the Deployment again, a full secrets audit across both environments, and uptime monitoring on the public URL. 10–14 working days. €2,490

These are fixed prices — no hourly billing, no calls. The diagnosis stands alone: you keep the report whether or not you hire the repair.


Why This Keeps Happening

Vibe-coding tools optimize for the moment of "wow": the preview, the demo, the working button. They do not prompt you to reconcile two environments, because the preview is the product they're selling, and the Deployment is an afterthought you reach only when you're ready to share.

But the preview is not production. It's a warm, fully-proxied, secret-rich illusion of one — and the distance between the two is exactly the distance between "it works for me" and a customer looking at a 404. RESCUE exists to close that distance.

Have a similar problem?

We build and fix production systems at a fixed price.

Talk to FIRME