Frontend Development 17 min read

Refactoring the External Product Detail Page: SSR Migration, Request Interceptor and Tracking Hook Redesign

This article details the complete redesign of the external product detail page, replacing the uni‑app SPA with a source‑build SSR solution, introducing a split‑first‑screen data strategy, multi‑environment support, risk‑controlled fallback mechanisms, and targeted refactors of request interceptors and tracking hooks, resulting in significant performance and business metric improvements.

Architect
Architect
Architect
Refactoring the External Product Detail Page: SSR Migration, Request Interceptor and Tracking Hook Redesign

The external product detail page (H5/mini‑program) previously relied on the legacy detailV3 API, causing style and functionality gaps with the latest app version and leading to poor performance metrics such as high First Meaningful Paint (FMP) and Largest Contentful Paint (LCP) times.

To close the gap, the team adopted the newest backend API, migrated the page to a source‑build Server‑Side Rendering (SSR) architecture, and aimed to dramatically improve front‑end load speed while preserving the ability to run the same code across multiple environments.

Source‑build Overview – Source‑build is a front‑end platform based on SSR that lets developers focus on business logic while the platform handles component libraries, performance, and stability. Pages are assembled via a visual builder, and pipelines automatically build and upload the output.

First‑Screen Performance Guarantee – The core requirement was to accelerate the first‑screen experience. Because the detail data is sensitive and encrypted, the solution splits the data: non‑encrypted first‑screen fields are fetched during the SSR phase and rendered directly as HTML, while the encrypted remainder is requested later on the client side. This preserves both speed and security.

The simplified flow is:

1. Browser requests page → SSR server fetches first‑screen data → HTML is returned.
2. Browser loads page, then client‑side code requests encrypted data → data is merged into the page.

Multi‑Environment Execution – In H5 the SSR page is accessed directly. For mini‑programs, the built‑in webview component loads the same SSR URL, allowing a single code base to run in both web and mini‑program contexts.

Risk Control & Fallback Strategies – To avoid service disruption, three fallback mechanisms were introduced:

Old detail page remains available as a downgrade path.

If SSR fails, only the first‑screen data is affected, keeping the rest of the business flow functional.

Gradual gray‑release via the front‑end config center enables controlled rollout and instant rollback if issues arise.

Targeted Refactor 1 – Request Interceptor – A new RequestInceptor TypeScript interface separates node‑side and client‑side logic, preventing environment‑specific errors. The loader composes interceptors into a promise chain.

export interface RequestInceptor
> {
  (): {
    // node environment interceptor
    nodeEnv: (config: T, runtimeConfig?: RunTimeConfig) => Promise
| T;
    // browser environment interceptor
    clientEnv: (config: T, runtimeConfig?: Pick
) => Promise
| T;
  };
}

const h5CommonHeaders: RequestInceptor = () => ({
  nodeEnv: config => {
    config.headers['reqEnv'] = 'node';
    return config;
  },
  clientEnv: async config => {
    config.headers['appid_org'] = 'wxapp';
    return config;
  },
});

const inceptorsLoader = async (initialConfig, inceptors) => {
  const promiseList = inceptors.map(interceptor => async config => {
    const { nodeEnv, clientEnv } = interceptor();
    return isInWindow ? clientEnv(config, config?.runTimeConfig) : nodeEnv(config, config?.runTimeConfig);
  });
  const result = await promiseList.reduce((p, fn) => p.then(fn), Promise.resolve(initialConfig));
  return result;
};

export const requestInceptorsCreator = config =>
  inceptorsLoader(config, [h5CommonHeaders, yunDunSDK]);

Targeted Refactor 2 – Tracking Hook – The tracking system was rebuilt as a React Hook, allowing developers to import useProTrack and obtain type‑safe tracking functions without manual configuration.

const generateTrackConfig = (trackSend) => {
  return function createTrackConfig() {
    const names = trackSend.map(item => item.name);
    const extractEventData = current => {
      const nameSplit = current.split('_');
      const [page, block] = nameSplit.slice(-2);
      const isBlockTypePresent = /\d+/.test(page);
      const event = nameSplit.slice(0, isBlockTypePresent ? -2 : -1).join('_');
      return { event, current_page: isBlockTypePresent ? page : block, block_type: isBlockTypePresent ? block : '' };
    };
    return names.reduce((total, current) => {
      const eventData = extractEventData(current);
      total[current] = (platform, { transParams }) => createEventObject(eventData, platform, transParams);
      return total;
    }, {});
  };
};

export const useProTrack = ({ props, functionalRef }, trackSendProps) => {
  useWithReactFunctionalTrack({
    functionalRef,
    functionalProps: functionalRef.current,
    useEffect,
    createTrackConfig: generateTrackConfig(trackSendRef.current),
  });
  useEffect(() => {
    ObserveTrackRef.current = new IntersectionObserver(handleIntersection);
    return () => ObserveTrackRef.current?.disconnect();
  }, []);
  const trackFuncMemo = useMemo(() => {
    return trackSendRef.current.reduce((result, item) => {
      result[item.name] = (trackParams, options) => {
        if (!options?.ele) {
          track(item.name)(trackParams);
          return;
        }
        const { ele } = options;
        startToObserveMap.current[item.name]();
      };
      return result;
    }, {});
  }, []);
  return trackFuncMemo;
};

// Example usage
const proTrack = useProTrack({ props, functionalRef }, [
  { name: 'trackEvent_1234' },
  { name: 'trackEvent_2345' },
]);
proTrack.trackEvent_1234({ button_title: 'I know' });

Performance Gains – After the refactor, the average FMP dropped from 2.75 s to 1.06 s (≈61 % improvement) and the 75th‑percentile FMP from 2.74 s to 1.13 s. Average LCP fell from 3.29 s to 1.20 s (≈64 % improvement) and the 75th‑percentile LCP from 4.06 s to 1.37 s.

Business Impact – On the H5 platform, the “Buy Now” click‑through rate increased by ~2.96 pp and the “Confirm Order” click‑through rate by ~0.92 pp; similar gains were observed on the mini‑program side.

In summary, the migration to a source‑build SSR architecture, the split‑first‑screen data strategy, and the modular refactors of request interceptors and tracking hooks together delivered a markedly faster, more secure, and easier‑to‑maintain external product detail experience, while laying a solid foundation for future multi‑environment extensions.

frontendperformanceSSRrefactoringrequest interceptortracking hook
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

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.