DEV Community

Shashank
Shashank

Posted on

Faster Frontend Rollbacks with Versioned Builds

How we went from "rebuild and restart every pod" to "pick a build and click deploy."

Every team ships a bad build at some point. When it happens, the only thing that matters is how quickly you can get back to the last version that worked.


The problem

A single Node server served both our backend and frontend from one domain:

example.com/api/...   →  Backend
example.com/...       →  Frontend (HTML, JS, CSS)
Enter fullscreen mode Exit fullscreen mode

The frontend files were cached inside every pod. To revert a bad build, we had to revert the code, rebuild, redeploy and restart every pod. That's a full deploy cycle in the middle of an incident.


The idea: never overwrite a build

Every build goes to its own folder in S3, served through a CDN. At the root sits one special file: index.html, a copy of the index.html from the build we want live.

s3://frontend-bucket/            (https://cdn.example.com)
├── index.html          ← copy of the LIVE build's index.html
├── 20261028-1015/      ← build 1
│   ├── index.html
│   ├── main.js
│   └── style.css
└── 20261029-1430/      ← build 2 (latest)
    ├── index.html      ── copied to root on release
    ├── main.js
    └── style.css
Enter fullscreen mode Exit fullscreen mode

Old builds are never touched. Which version is live comes down to which index.html is sitting at the root.

The one detail that makes it work: each build must load its assets by the full folder path, so it still works after being copied to the root:

<script src="https://cdn.example.com/20261029-1430/main.js"></script>
Enter fullscreen mode Exit fullscreen mode

Set this with your bundler's publicPath or base option. As a bonus, View Source shows which build is live.


Serving the HTML from the CDN

The pods no longer hold frontend files. Requests to /api/* go to Node, and everything else gets the root index.html from the CDN. You can route this in two ways:

  • At the load balancer: non-/api requests go straight to the CDN. The frontend stays up even if Node is down.
  • Through Node: Node fetches index.html from the CDN and streams it. This is a smaller change, but the frontend depends on Node.

Either way, a frontend change never needs a pod restart again.


Release and rollback

Release:

Build → Upload to new folder → Verify → Copy its index.html to root → Clear CDN cache
Enter fullscreen mode Exit fullscreen mode

You can test any build at cdn.example.com/<timestamp>/index.html before users see it. Only the copy-to-root step changes what users get.

The root index.html acts as a switch between builds. We automate release and rollback through Jenkins: select a build and run the job. Jenkins replaces the root HTML, clears the CDN cache and verifies the live version, with no manual file changes.

It takes about a minute. There's no code revert, no rebuild and no pod restart. You revert the bad commit later, without pressure.

One assumption: backend changes stay backward compatible with the previous frontend build. The frontend and backend used to ship together. Now the frontend can roll back on its own, which is what makes it fast.


Two rules you can't skip

  • Caching: the root index.html uses no-cache, because it's the switch. Timestamped JS/CSS use max-age=31536000, immutable. If index.html is cached for an hour, your rollback takes an hour to reach users.
  • Retention: keep the last ~10 builds. Never delete the live build or the one before it, because users with the page open still load from it.

Serving straight from a CDN? This works for you too

Many teams serve the frontend directly from S3 behind a CDN, with no Node server. The weak spot is that every deploy overwrites the previous build. A bad release means reverting, merging to main and waiting for the pipeline: 10 minutes at the very least.

Versioned folders fit this setup even better, because there's no routing to change. Upload each build to a new folder, use full asset paths, and treat the root index.html as the switch.

This is especially worth it if you deploy once or twice a week. Ten builds give you over a month of rollback points for almost no storage cost, and recovery becomes a one-minute switch instead of a 10-minute scramble.


Wrapping up

Builds are immutable, and going live is just pointing at one of them. Once releases work that way, a rollback is no longer an emergency rebuild. It's a one-minute switch.

Top comments (0)