Second‑Level (秒级) Front‑End Rollback: A Step‑by‑Step Demo Using Vite, React, and Node
This article presents a complete tutorial for implementing a near‑instant (second‑level) rollback mechanism for single‑page front‑end applications, covering the underlying idea, project setup with Vite and React, history recording, a lightweight Node server, and a petite‑Vue visual interface for selecting and applying previous builds.
1. Introduction
This article is part of the "Front‑End Engineering" series and introduces the "quick rollback" feature, which is crucial for front‑end projects that need to revert to a previous version within seconds after a faulty deployment.
Typical rollback involves git reset or git revert, rebuilding and redeploying, which can take several minutes. A faster, second‑level solution is required.
While Docker can achieve this, many companies still host static assets via nginx or upload them to an OSS and serve them through a CDN.
The article provides a hands‑on demo that you can follow to understand the whole process and apply it to your own projects.
2. Concept
After each build, a SPA generates an index.html that references version‑specific static assets. By preserving every build's index.html and the associated assets (either locally in dist or on OSS), we can later replace the current index.html with a previous one, instantly switching the site to that version.
The approach works for any SPA framework (React, Vue, etc.) and any bundler (Webpack, Vite).
3. Implementation
3.1 Prepare a SPA project
Create a React + Vite project:
# npm 6.x
npm create vite@latest quick_rollback --template react-ts
# npm 7+, need double dash
npm create vite@latest quick_rollback -- --template react-tsInstall dependencies:
cd quick_rollback
npm installModify vite.config.ts so that the dist folder is not cleared on each build:
build: {
emptyOutDir: false, // keep previous output
}Run the first build to generate dist:
npm run buildChange src/App.tsx to differentiate the builds, then rebuild. The dist folder now contains both versions of the static assets.
3.2 Save historical index.html content
Create build.mjs (ES module) that records the built index.html into history.json after each build:
// build.mjs
console.log('Recording build history');Update package.json build script:
"build": "tsc && vite build && node build.mjs",The script creates history.json (if missing) with a {"list":[]} structure, reads the generated dist/index.html, pushes an entry containing the timestamp, HTML content, and a random ID, then writes the updated JSON back.
import path from 'path';
import fs from 'fs';
function start() {
const historyPath = path.resolve('history.json');
if (!fs.existsSync(historyPath)) {
fs.writeFileSync(historyPath, JSON.stringify({ list: [] }));
}
const html = fs.readFileSync(path.resolve('./dist/index.html'), 'utf-8');
const history = JSON.parse(fs.readFileSync(historyPath, 'utf-8'));
history.list.push({
time: new Date().toLocaleString('zh-cn'),
html,
id: Math.random().toString(16).substr(2)
});
fs.writeFileSync(historyPath, JSON.stringify(history, null, 2));
}
start();After a couple of builds, history.json holds two entries with their respective index.html snapshots.
3.3 Host the built assets
Install a static server globally:
npm i serve -gServe the dist folder:
serve -s dist3.4 Node server for rollback
Create server.mjs that provides three endpoints: / – returns rollback.html (the visual UI). /history – returns the content of history.json as JSON. /rollback?id=... – finds the matching entry by ID and overwrites dist/index.html with its saved HTML.
import http from 'http';
import url from 'url';
import fs from 'fs';
import path from 'path';
const server = http.createServer((req, res) => {
const { pathname } = url.parse(req.url, true);
const method = req.method.toLowerCase();
if (pathname === '/' && method === 'get') {
res.writeHead(200, { 'Content-Type': 'text/html' }, 'utf-8');
res.end(fs.readFileSync(path.resolve('./rollback.html'), 'utf-8'));
return;
}
if (pathname === '/history' && method === 'get') {
res.writeHead(200, { 'Content-Type': 'application/json' }, 'utf-8');
res.end(JSON.stringify({
code: 200,
message: 'Success',
data: JSON.parse(fs.readFileSync(path.resolve('./history.json'), 'utf-8'))
}));
return;
}
if (pathname === '/rollback' && method === 'get') {
const { query } = url.parse(req.url, true);
const { id } = query;
const history = JSON.parse(fs.readFileSync(path.resolve('./history.json'), 'utf-8'));
const html = history.list.find(item => item.id === id).html;
fs.writeFileSync(path.resolve('./dist/index.html'), html);
res.writeHead(200, { 'Content-Type': 'application/json' }, 'utf-8');
res.end(JSON.stringify({ code: 200, message: 'Success', data: {} }));
return;
}
});
server.listen(3001, () => {
console.log('server is running on http://localhost:3001');
});3.5 Front‑end visual rollback page
The UI uses petite‑vue (a tiny Vue‑compatible library). It fetches the build list from /history, populates a <select> with timestamps, and calls /rollback?id=... when the user confirms.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>rollback</title>
</head>
<body>
<script src="https://cdn.bootcdn.net/ajax/libs/petite-vue/0.4.1/petite-vue.umd.min.js"></script>
<div id="app" @vue:mounted="onMounted">
<select v-model="currentItem">
<option value="">请选择回滚版本</option>
<option v-for="item in historyList" :key="item.id" :value="item">
发版时间:{{ item.time }}
</option>
</select>
<button @click="onRollback">回滚</button>
</div>
<script>
PetiteVue.createApp({
historyList: [],
currentItem: undefined,
onMounted() { this.getHistory(); },
getHistory() {
fetch('/history')
.then(res => res.json())
.then(res => { if (res.code === 200) this.historyList = res.data.list; });
},
onRollback() {
if (!this.currentItem) return alert('请选择回滚目标版本!');
const confirmRollback = confirm(`确认项目回滚到 ${this.currentItem.time} 版本!`);
if (confirmRollback) {
fetch(`/rollback?id=${this.currentItem.id}`)
.then(res => res.json())
.then(res => { if (res.code === 200) alert('快速回滚成功!'); });
}
}
}).mount('#app');
</script>
</body>
</html>3.6 Testing the rollback
Serve the dist folder, start the Node server, open http://localhost:3001, select an older build, confirm the rollback, and observe the front‑end page instantly switch to the previous version.
4. Conclusion
Keeping historical build artifacts prevents two common problems: (1) the dist folder being cleared during a new build, causing 404s, and (2) lazy‑loaded routes breaking after a deployment because the previous files no longer exist.
The approach is simple, low‑cost, and works for any SPA. To avoid unbounded storage growth, the tutorial suggests keeping only the latest five builds and cleaning older assets.
Future work includes a Docker‑based solution that can handle both front‑end and back‑end projects.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
