{"course":{"slug":"building-apps-with-ai","title":"Building Apps with AI","url":"https://addaly.com/learn/building-apps-with-ai","level":"beginner","track":"build"},"lesson":{"slug":"which-version-is-live","title":"Which version is live","url":"https://addaly.com/learn/building-apps-with-ai/which-version-is-live","position":46,"of":87,"minutes":8,"answer":"A failed build leaves the last good deployment serving, so 'I pushed and nothing changed' is usually a build log you have not read yet.","keyIdea":"A failed build leaves the last good deployment serving, so 'I pushed and nothing changed' is usually a build log you have not read yet."},"body":{"format":"markdown","text":"## I pushed and nothing changed\n\nFive causes, in the order to check them.\n\n1. **The build failed.** The host keeps serving the last good deployment, so the site looks unchanged rather than broken. Nothing tells you unless you look, or unless you turned on the email notification.\n2. **You pushed a different branch.** Only the production branch, usually `main`, becomes the live site. Everything else becomes a preview.\n3. **Your browser is holding the old page.** A hard refresh, or a private window, settles it.\n4. **The change was to a build-time value.** A variable edited on the dashboard without a redeploy, from the previous lesson.\n5. **The change did not do what you think.** Only reachable once the four above are excluded.\n\n## Reading the deploy log\n\nEvery host has a list of deployments, each with the commit id it built, a status, and a log. Open the log of the failed one and scroll to the first error, not the last line. Build tools print a wall of `npm ERR!` lines after the real problem, and the real problem is usually thirty lines above the bottom, saying something specific: a type error with a file and line, a missing module, a variable that is undefined.\n\nMost build failures reproduce locally with `npm run build`. Run it before every push (the earlier lesson put it in the rules file), and the deploy log becomes something you read rarely.\n\n> Figure: I pushed and nothing changed. Steps: 1. Build failed (The last good deployment is still serving); 2. Wrong branch (Only the production branch becomes the live site); 3. Stale page (Hard refresh, or a private window); 4. Build-time value (A variable changed without a redeploy); 5. The change itself (It did not do what you think it did). Check them in this order. The last one is only reachable once the four before it are excluded, and it is where most people start.\n\n## Stamp the version\n\nThe lasting fix for \"which version is live\" is to make the running app say so. Hosts expose the commit id to the build as a variable: `VERCEL_GIT_COMMIT_SHA` on Vercel, `CF_PAGES_COMMIT_SHA` on Cloudflare Pages, `COMMIT_REF` on Netlify, `RENDER_GIT_COMMIT` on Render. Read it at build time and print the first seven characters in the footer, or serve it from a route:\n\n```typescript\n// app/api/version/route.ts\nexport function GET() {\n  return Response.json({ sha: (process.env.VERCEL_GIT_COMMIT_SHA ?? \"dev\").slice(0, 7) });\n}\n```\n\nNow `git log --oneline -1` on your machine and `/api/version` on the site can be compared, and \"is my fix live\" is a fact rather than a feeling. Add a `started` timestamp too, and the same route tells you whether the process restarted.\n\n## Caches\n\nModern builds give JavaScript files hashed names (`main.a3f9c21.js`), so a new build produces new filenames and browsers cannot serve stale code by accident. The HTML that references them can still be cached briefly by the browser or the CDN, which is the minute of lag after a deploy.\n\nThe exception is a progressive web app with a service worker. Service workers cache aggressively and serve the old version until the new worker has installed, which can take a reload or two. If an agent added PWA support because it seemed nice, this is the cost.\n\n## Rolling back\n\nBecause hosts keep every deployment, going back is a button: \"promote to production\" on an older deployment on Vercel, \"publish deploy\" on Netlify, \"rollback\" on Cloudflare Pages. It takes seconds and no rebuild.\n\nWhat it does not roll back is the database. If the new version ran a migration, the old code now runs against a schema it has never seen, and may fail in a different way. That is the reason for the rule in the data block that migrations add columns first and remove them in a later release, once no live version still reads them. A rollback is only safe when the code before and the code after both understand the current tables.\n\n## Previews\n\nPush a branch and most hosts build it to its own URL: `app-git-payments-you.vercel.app` or similar. Open that on your phone, send it to a friend, run the stranger's checklist on it, all before anything reaches `main`. Preview deployments use the variables scoped to the preview environment, which is why the previous lesson insisted on a separate database for them.\n\n## Knowing it is still up\n\nA health route is a page that returns quickly and says yes:\n\n```typescript\nexport function GET() {\n  return Response.json({ ok: true, sha: process.env.VERCEL_GIT_COMMIT_SHA?.slice(0, 7) });\n}\n```\n\nFree monitors (UptimeRobot and Better Stack both have free tiers) request it every few minutes and email you when it stops answering. That is a way to learn your site is down before a user does, and it costs nothing but the two minutes of setup."},"check":{"question":"You push a fix. The site still shows the bug. The host's dashboard shows the newest deployment marked Failed. What is the site serving?","options":[{"text":"Nothing; the site is down until the build is fixed.","correct":false,"tempting":"Hosts keep serving the last successful build precisely so that a broken push does not take the site down."},{"text":"The failed build, with whatever parts compiled.","correct":false,"tempting":"A build either completes or it does not; hosts never serve a partial one."},{"text":"A cached copy held by the CDN, which will refresh on its own within a few minutes of the push.","correct":false,"tempting":"Caches can delay a change for minutes, but a Failed status means the change never became a deployment to cache."},{"text":"The last successful deployment, which is why the bug is still there.","correct":true}]},"prev":{"slug":"environment-variables-and-the-two-worlds","title":"Environment variables, and the prefix that leaks","url":"https://addaly.com/learn/building-apps-with-ai/environment-variables-and-the-two-worlds"},"next":{"slug":"a-domain-of-your-own","title":"A domain of your own","url":"https://addaly.com/learn/building-apps-with-ai/a-domain-of-your-own"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Which version is live', in Building Apps with AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/building-apps-with-ai/which-version-is-live (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Which version is live","courseTitle":"Building Apps with AI","module":{"index":5,"slug":"save-points-and-shipping","title":"Save points, and shipping to a stranger"},"answer":null,"keyIdea":"A failed build leaves the last good deployment serving, so ‘I pushed and nothing changed’ is usually a build log you have not read yet.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure","figure":{"type":"flow","steps":[{"label":"Build failed","detail":"The last good deployment is still serving"},{"label":"Wrong branch","detail":"Only the production branch becomes the live site"},{"label":"Stale page","detail":"Hard refresh, or a private window"},{"label":"Build-time value","detail":"A variable changed without a redeploy"},{"label":"The change itself","detail":"It did not do what you think it did"}],"title":"I pushed and nothing changed","caption":"Check them in this order. The last one is only reachable once the four before it are excluded, and it is where most people start."},"alt":"Figure: I pushed and nothing changed. Steps: 1. Build failed (The last good deployment is still serving); 2. Wrong branch (Only the production branch becomes the live site); 3. Stale page (Hard refresh, or a private window); 4. Build-time value (A variable changed without a redeploy); 5. The change itself (It did not do what you think it did). Check them in this order. The last one is only reachable once the four before it are excluded, and it is where most people start."}},"blocks":[{"t":"h2","id":"i-pushed-and-nothing-changed","runs":[{"text":"I pushed and nothing changed"}]},{"t":"p","runs":[{"text":"Five causes, in the order to check them."}]},{"t":"ol","start":1,"items":[[{"text":"The build failed.","b":true},{"text":" The host keeps serving the last good deployment, so the site looks unchanged rather than broken. Nothing tells you unless you look, or unless you turned on the email notification."}],[{"text":"You pushed a different branch.","b":true},{"text":" Only the production branch, usually "},{"text":"main","code":true},{"text":", becomes the live site. Everything else becomes a preview."}],[{"text":"Your browser is holding the old page.","b":true},{"text":" A hard refresh, or a private window, settles it."}],[{"text":"The change was to a build-time value.","b":true},{"text":" A variable edited on the dashboard without a redeploy, from the previous lesson."}],[{"text":"The change did not do what you think.","b":true},{"text":" Only reachable once the four above are excluded."}]]},{"t":"h2","id":"reading-the-deploy-log","runs":[{"text":"Reading the deploy log"}]},{"t":"p","runs":[{"text":"Every host has a list of deployments, each with the commit id it built, a status, and a log. Open the log of the failed one and scroll to the first error, not the last line. Build tools print a wall of "},{"text":"npm ERR!","code":true},{"text":" lines after the real problem, and the real problem is usually thirty lines above the bottom, saying something specific: a type error with a file and line, a missing module, a variable that is undefined."}]},{"t":"p","runs":[{"text":"Most build failures reproduce locally with "},{"text":"npm run build","code":true},{"text":". Run it before every push (the earlier lesson put it in the rules file), and the deploy log becomes something you read rarely."}]},{"t":"h2","id":"stamp-the-version","runs":[{"text":"Stamp the version"}]},{"t":"p","runs":[{"text":"The lasting fix for “which version is live” is to make the running app say so. Hosts expose the commit id to the build as a variable: "},{"text":"VERCEL_GIT_COMMIT_SHA","code":true},{"text":" on Vercel, "},{"text":"CF_PAGES_COMMIT_SHA","code":true},{"text":" on Cloudflare Pages, "},{"text":"COMMIT_REF","code":true},{"text":" on Netlify, "},{"text":"RENDER_GIT_COMMIT","code":true},{"text":" on Render. Read it at build time and print the first seven characters in the footer, or serve it from a route:"}]},{"t":"code","lang":"typescript","label":"TypeScript","text":"// app/api/version/route.ts\nexport function GET() {\n  return Response.json({ sha: (process.env.VERCEL_GIT_COMMIT_SHA ?? \"dev\").slice(0, 7) });\n}"},{"t":"p","runs":[{"text":"Now "},{"text":"git log --oneline -1","code":true},{"text":" on your machine and "},{"text":"/api/version","code":true},{"text":" on the site can be compared, and “is my fix live” is a fact rather than a feeling. Add a "},{"text":"started","code":true},{"text":" timestamp too, and the same route tells you whether the process restarted."}]},{"t":"h2","id":"caches","runs":[{"text":"Caches"}]},{"t":"p","runs":[{"text":"Modern builds give JavaScript files hashed names ("},{"text":"main.a3f9c21.js","code":true},{"text":"), so a new build produces new filenames and browsers cannot serve stale code by accident. The HTML that references them can still be cached briefly by the browser or the CDN, which is the minute of lag after a deploy."}]},{"t":"p","runs":[{"text":"The exception is a progressive web app with a service worker. Service workers cache aggressively and serve the old version until the new worker has installed, which can take a reload or two. If an agent added PWA support because it seemed nice, this is the cost."}]},{"t":"h2","id":"rolling-back","runs":[{"text":"Rolling back"}]},{"t":"p","runs":[{"text":"Because hosts keep every deployment, going back is a button: “promote to production” on an older deployment on Vercel, “publish deploy” on Netlify, “rollback” on Cloudflare Pages. It takes seconds and no rebuild."}]},{"t":"p","runs":[{"text":"What it does not roll back is the database. If the new version ran a migration, the old code now runs against a schema it has never seen, and may fail in a different way. That is the reason for the rule in the data block that migrations add columns first and remove them in a later release, once no live version still reads them. A rollback is only safe when the code before and the code after both understand the current tables."}]},{"t":"h2","id":"previews","runs":[{"text":"Previews"}]},{"t":"p","runs":[{"text":"Push a branch and most hosts build it to its own URL: "},{"text":"app-git-payments-you.vercel.app","code":true},{"text":" or similar. Open that on your phone, send it to a friend, run the stranger’s checklist on it, all before anything reaches "},{"text":"main","code":true},{"text":". Preview deployments use the variables scoped to the preview environment, which is why the previous lesson insisted on a separate database for them."}]},{"t":"h2","id":"knowing-it-is-still-up","runs":[{"text":"Knowing it is still up"}]},{"t":"p","runs":[{"text":"A health route is a page that returns quickly and says yes:"}]},{"t":"code","lang":"typescript","label":"TypeScript","text":"export function GET() {\n  return Response.json({ ok: true, sha: process.env.VERCEL_GIT_COMMIT_SHA?.slice(0, 7) });\n}"},{"t":"p","runs":[{"text":"Free monitors (UptimeRobot and Better Stack both have free tiers) request it every few minutes and email you when it stops answering. That is a way to learn your site is down before a user does, and it costs nothing but the two minutes of setup."}]}],"check":{"question":[{"text":"You push a fix. The site still shows the bug. The host’s dashboard shows the newest deployment marked Failed. What is the site serving?"}],"options":[{"text":[{"text":"Nothing; the site is down until the build is fixed."}],"correct":false,"tempting":[{"text":"Hosts keep serving the last successful build precisely so that a broken push does not take the site down."}]},{"text":[{"text":"The failed build, with whatever parts compiled."}],"correct":false,"tempting":[{"text":"A build either completes or it does not; hosts never serve a partial one."}]},{"text":[{"text":"A cached copy held by the CDN, which will refresh on its own within a few minutes of the push."}],"correct":false,"tempting":[{"text":"Caches can delay a change for minutes, but a Failed status means the change never became a deployment to cache."}]},{"text":[{"text":"The last successful deployment, which is why the bug is still there."}],"correct":true,"tempting":null}],"order":[2,0,1,3],"why":null},"prev":{"slug":"environment-variables-and-the-two-worlds","title":"Environment variables, and the prefix that leaks","minutes":9},"next":{"slug":"a-domain-of-your-own","title":"A domain of your own","minutes":8}}}