Frontend Development 16 min read

Design and Implementation of a Frontend Version Upgrade Prompt for B2B Platforms

This article describes a comprehensive solution for notifying B2B web users about outdated frontend versions, covering the problem background, version number generation, trigger conditions, micro‑frontend adaptation, detailed plugin and React component code, integration steps for main and sub‑applications, debugging guidance, and measured user impact.

ByteFE
ByteFE
ByteFE
Design and Implementation of a Frontend Version Upgrade Prompt for B2B Platforms

Background

In B2B (TOB) platforms that rely mainly on PC pages, many users keep the page open for hours, so a new frontend version released in the afternoon may not reach more than 98% of users until the next day. Statistics from the BSCM platform show that about 6% of users (over 600 people per 10k UV) still use the old version after a 2‑3 pm release.

Solution

Popup Content

Trigger Conditions

The system distinguishes between a local version number (the version embedded in the HTML the user loads) and a cloud version number (the latest version generated when developers publish new code).

Timing of Condition Evaluation

Two possible moments to evaluate the trigger condition are:

When the frontend detects that a new version has been published (e.g., via WebSocket message).

When the frontend actively polls or checks during certain low‑frequency events (e.g., route changes, visibility changes).

These moments are compared on dimensions such as timeliness, number of checks, and implementation cost.

⭐️ Higher scores indicate better performance on the evaluated dimension.

The comparison shows that a combination of WebSocket push and frontend event listening is most suitable, but because daily releases are limited, event listening offers lower implementation cost and is preferred for practical deployment.

The visibilitychange event also satisfies the requirements for PC browsers in B‑end pages.

Version Number Generation

Local version number

The HTML file itself carries the local version; a webpack plugin can inject a version identifier into the HTML during the build.

Cloud version number

The cloud version can be stored in a JSON file, CDN, or database. Two practical approaches are used: generate version.json alongside the build and serve it via a route, or parse the version directly from the latest HTML file.

Micro‑frontend Adaptation

Both main and sub‑applications need independent version prompts without overlapping dialogs. Three problems must be solved:

Distinguish local version identifiers for each app within a single HTML file (handled by the plugin).

Request the correct cloud version for each app (main app fetches version.json , sub‑app parses its own HTML).

Prevent duplicate dialogs by checking whether a dialog is already displayed.

Specific Implementation

Writing and Reading Version Numbers

Listening Timing and Rate‑Limiting Logic

Version releases are low‑frequency events, but the check frequency should be limited to avoid excessive requests and user annoyance. A half‑hour cache and a daily expiration for the update prompt are applied.

Specific Code

Plugin (VersionPlugin)

/* eslint-disable */
import { CoraWebpackPlugin, WebpackCompiler } from '@ies/eden-web-build';
const fs = require('fs');
const path = require('path');
const cheerio = require('cheerio');

interface IVersion {
  name?: string; // build folder name
  subName?: string; // sub‑app name, optional for main app
}

export class VersionPlugin implements CoraWebpackPlugin {
  readonly name = 'versionPlugin';
  private _version: number;
  private _name: string;
  private _subName: string;
  constructor(params: IVersion) {
    this._version = new Date().getTime();
    this._name = params?.name || 'build';
    this._subName = params?.subName || '';
  }
  apply(compiler: WebpackCompiler): void {
    compiler.hooks.afterCompile.tap('versionPlugin', () => {
      try {
        const filePath = path.resolve(`./${this._name}/template/version.json`);
        fs.writeFile(filePath, JSON.stringify({ version: this._version }), (err) => {
          if (err) console.log('@@@err', err);
        });
        const htmlPath = path.resolve(`./${this._name}/template/index.html`);
        const data = fs.readFileSync(htmlPath);
        const $ = cheerio.load(data);
        $('body').append(`<div id="${this._subName}versionTag" style="display: none">${this._version}</div>`);
        fs.writeFile(htmlPath, $.html(), (err) => {
          if (err) console.log('@@@htmlerr', err);
        });
      } catch (err) {
        console.log(err);
      }
    });
  }
}

Popup Component (FeVersionWatcher)

import React, { useEffect } from 'react';
import { Modal } from '@ecom/auxo';
import axios from 'axios';
import moment from 'moment';

export interface IProps {
  isSub?: boolean; // whether it is a sub‑app
  subName?: string;
  resourceUrl?: string; // sub‑app resource URL
}
export type IType = 'visibilitychange' | 'popstate' | 'init';

export default React.memo
(props => {
  const { isSub = false, subName = '', resourceUrl = '' } = props || {};
  const cb = (latestVersion, currentVersion, type) => {
    try {
      if (latestVersion && currentVersion && latestVersion > currentVersion) {
        localStorage.setItem('versionUpdateExpireTime', moment().endOf('day').format('x'));
        if (!document.getElementById('versionModalTitle')) {
          Modal.confirm({
            title:
版本更新提示
,
            content: '您已经长时间未使用此页面,在此期间平台有过更新,如您此时在页面中没有填写相关信息等操作,请点击刷新页面使用最新版本!',
            okText:
刷新页面
,
            cancelText:
我知道了
,
            onCancel: () => { console.log('fe-version-watcher INFO: 未更新~'); },
            onOk: () => { location.reload(); },
          });
        }
      }
      localStorage.setItem('versionInfoExpireTime', String(Date.now() + 1000 * 60 * 30));
    } catch {}
  };
  const formatVersion = (text) => (text ? Number(text) : undefined);
  useEffect(() => {
    try {
      const fn = (type) => {
        if (document.visibilityState === 'visible') {
          if (Number(localStorage.getItem('versionUpdateExpireTime') || 0) >= Date.now()) return;
          if (Number(localStorage.getItem('versionInfoExpireTime') || 0) > Date.now()) return;
          if (!isSub) {
            const dom = document.getElementById('versionTag');
            const currentVersion = formatVersion(dom?.innerText);
            axios.get(`/version?timestamp=${Date.now()}`).then(res => {
              const latestVersion = res?.data?.version;
              cb(latestVersion, currentVersion, type);
            });
          } else {
            if (resourceUrl) {
              const dom = document.getElementById(`${subName}versionTag`);
              const currentVersion = dom?.innerText ? Number(dom.innerText) : undefined;
              axios.get(resourceUrl).then(res => {
                try {
                  const html = res.data;
                  const doc = new DOMParser().parseFromString(html, 'text/html');
                  const latestVersion = formatVersion(doc.getElementById(`${subName}versionTag`)?.innerText);
                  cb(latestVersion, currentVersion, type);
                } catch {}
              });
            }
          }
        }
      };
      const visibleFn = () => fn('visibilitychange');
      const routerFn = () => fn('popstate');
      if (isSub) fn('init');
      document.addEventListener('visibilitychange', visibleFn);
      window.addEventListener('popstate', routerFn);
      return () => {
        document.removeEventListener('visibilitychange', visibleFn);
        window.removeEventListener('popstate', routerFn);
      };
    } catch {}
  }, []);
  return
;
});

How to Integrate

Main Application Version

Install dependencies: npm i @ecom/fe-version-watcher-plugin # plugin npm i @ecom/logistics-supply-chain-fe-version-watcher # popup component

Import VersionPlugin in the webpack config to generate version.json and inject the version tag into HTML. import { VersionPlugin } from '@ecom/fe-version-watcher-plugin'; // webpack config module.exports = { // ... plugins: [ // other plugins [VersionPlugin, {}], ], };

Import and render the watcher component: import { FeVersionWatcher } from '@ecom/logistics-supply-chain-fe-version-watcher';

Add a route /version that serves the generated version.json (or any equivalent endpoint).

Sub‑Application Version

Install the same dependencies as the main app.

Configure VersionPlugin with a subName so the HTML contains a distinct version tag. import { VersionPlugin } from '@ecom/fe-version-watcher-plugin'; module.exports = { // ... plugins: [ // other plugins [VersionPlugin, { subName: 'general-supplier', name: 'build_cn' }], ], };

Render the watcher with matching props: import { FeVersionWatcher } from '@ecom/logistics-supply-chain-fe-version-watcher';

Debugging / Demonstration

Clear related values from localStorage .

Modify the version number in the HTML to a smaller value.

Navigate to a different route or hide/show the page to trigger the popup.

Benefit Statistics

Four releases at 2‑3 pm show a clear decline in the UV share of old‑version users.

Since launch, the system has prompted over 100 k users and helped roughly 50 k users update to the latest frontend code.

Join Us

📬 The Douyin E‑commerce Frontend team for logistics and supply‑chain is responsible for platforms such as Logistics Management Center, Supply‑Chain Management Platform, and WMS.

We welcome interested engineers to read the original article or scan the QR code below to apply.

Beijing / Hangzhou

reactPluginmicrofrontendVersioningnotification
ByteFE
Written by

ByteFE

Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.