Frontend Development 12 min read

Integrating React‑Native Components into Web with React‑Web: Practices, Issues, and Optimizations

The article describes how to reuse React‑Native code on the web with react‑web by mapping components during bundling, handling platform‑specific layout and flexbox differences, and applying optimizations such as server‑side rendering, on‑demand imports, Preact substitution, and CSS extraction, which together cut bundle size by up to 70 % and halve first‑paint time.

Tencent Music Tech Team
Tencent Music Tech Team
Tencent Music Tech Team
Integrating React‑Native Components into Web with React‑Web: Practices, Issues, and Optimizations

In the past two years React‑Native has become extremely popular for building native applications. Replacing H5 pages with React‑Native can significantly improve user experience, but many scenarios still require a web version—for sharing, SEO, or as a fallback when the native app fails. Instead of maintaining separate codebases, the same React‑Native source can be reused on the web by providing equivalent web components and APIs and mapping them during the webpack bundling process.

There are existing open‑source implementations of web counterparts for React‑Native components, such as Taobao's react‑web . The original project is no longer actively maintained, so a copy was taken and maintained internally.

The project structure typically includes index.web.js as the web entry point, with index.ios.js and index.android.js for the native platforms.

import {Component} from 'react'
import {StyleSheet, View, Text} from 'react-native'
import { connect } from 'react-redux'
import ChorusItem from './ChorusItem'
import Gotobar from './Gotobar'
class Chorus extends Component {
        constructor(props) {
          super(props);
        }
        render(params) {
          const {  chorus, dispatch } = this.props;
          return (
{'合唱推荐'}
{chorus.map((item, index) =>
)}
)
        }
}
var styles = StyleSheet.create({
    itemColumn: {
        justifyContent: 'flex-start'
    }
})
module.exports = connect((state)=>{
  return {
    chorus: state.indexPage.chorus
  }
})(Chorus)

When platform‑specific differences arise, they can be handled with conditional code. Small differences are often resolved using Platform.OS checks:

_onScroll(e) {
    let event = e.nativeEvent, y;
    if (Platform.OS == 'web') {
      common.event.emit('scroll.change', event.target.scrollTop)
      common.event.emit('scroll.report', event.target.scrollTop)
    }
}

For larger divergences, it is advisable to abstract the component per platform and let webpack map the appropriate implementation (e.g., RouterContext.web.js for web and RouterContext.js for native).

Two major issues were encountered during the practice:

Layout consistency: To keep the same layout as native, a fixed‑height screen with absolute + overflow:scroll was used. On iOS this caused bounce effects that prevented scrolling in the opposite direction. The solution was to remove the fixed‑height layout and use a regular flow layout, adding position:fixed for sticky headers, footers, and overlays on the web.

Flex compatibility: React‑Native relies on flexbox, while web browsers support three flex specifications (2009, 2012, final). The 2009 spec is needed for older Android browsers and requires property mapping and vendor prefixes. The following mapping code was added: //2009 flex property mapping var flexboxProperties = { flex: "WebkitBoxFlex", order: "WebkitBoxOrdinalGroup", flexDirection: "WebkitBoxOrient", alignItems: "WebkitBoxAlign", justifyContent: "WebkitBoxPack", flexWrap: null, alignSelf: null, }; var oldFlexboxValues = { "flex-end": "end", "flex-start": "start", "space-between": "justify", "space-around": "distribute", };

Optimizations performed:

Server‑side rendering (SSR) was enabled to reduce first‑paint time. In a test environment (no cache, Wi‑Fi, laptop i5, 8 GB RAM) the original bundle (≈100 KB) took about 300 ms for download + execution. After SSR the HTML is delivered in under 200 ms.

On‑demand component loading reduced bundle size dramatically. Example transformation:

import {StyleSheet, View} from 'react-native'   →   import View from 'react-native/View/View.web'
import StyleSheet from 'react-native/View/View.web'

Before on‑demand imports the compressed bundle was ~300 KB; after it dropped to ~80 KB for the most common components.

Replacing React with Preact (a lightweight, compatible implementation) reduced the core library size from ~160 KB to ~38 KB. Event handling had to be adapted because Preact binds events directly to the DOM. The following event‑mapping snippet was added:

const EVENT_MAP = {
 'onStartShouldSetResponder': 'onTouchStart',
 'onMoveShouldSetResponder': 'onTouchMove',
 'onStartShouldSetResponderCapture': 'onTouchStartCapture',
 'onMoveShouldSetResponderCapture': 'onTouchMoveCapture',
 'onResponderGrant': 'onTouchStart',
 'onResponderTerminate': 'onTouchCancel',
 'onResponderMove': 'onTouchMove',
 'onResponderRelease': 'onTouchEnd'
};
if (name[0]=='o' && name[1]=='n') {
 let useCapture = name !== (name=name.replace(/Capture$/, ''));
 name = name.toLowerCase().substring(2);
 if (value) {
   if (!old) node.addEventListener(name, eventProxy, useCapture);
 } else {
   node.removeEventListener(name, eventProxy, useCapture);
 }
 (node._listeners || (node._listeners = {}))[name] = value;
}

Extracting inline styles to external CSS via a custom Babel plugin reduced runtime style calculations and allowed caching. The plugin walks the AST, finds StyleSheet.create calls, generates hashed class names, and replaces style props with className . Example conversion:

// before
let styles = StyleSheet.create({
  subtitleContainer: { flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center' },
  subtitleText: { fontSize: txt_1, color: '#F04F43' },
  iconRight: { height: 7, width: 7, borderTopWidth: 2, borderRightWidth: 2, borderColor: '#F04F43', borderStyle: 'solid', transform: [{ 'rotate': '45deg' }] }
});
查看特权
// after (CSS classes are hashed)
var styles = __WEBPACK_IMPORTED_MODULE_4_react_native_StyleSheet_StyleSheet_web__["a"].create({
  subtitleText: { fontSize: txt_1 }
});
<__WEBPACK_IMPORTED_MODULE_0_react___default.a.createElement(
  __WEBPACK_IMPORTED_MODULE_1_react_native_View_View_web__["a"],
  { className: "_17cytc2", style: styles.subtitleContainer },
  __WEBPACK_IMPORTED_MODULE_0_react___default.a.createElement(
    __WEBPACK_IMPORTED_MODULE_2_react_native_Text_Text_web__["a"],
    { className: "osilsm", style: styles.subtitleText },
    "\u67E5\u770B\u7279\u6743"
  ),
  __WEBPACK_IMPORTED_MODULE_0_react___default.a.createElement(__WEBPACK_IMPORTED_MODULE_1_react_native_View_View_web__["a"], { className: "_41z5ub", style: styles.iconRight })
);

Performance comparison (desktop Chrome 61, i7, Wi‑Fi):

Script load & execution time reduced from 168 ms to 125 ms.

Bundle size decreased from 251 KB to 117 KB.

Component render time dropped from 105 ms to 86 ms.

First‑paint (time to interactive) improved from 292 ms to 230 ms.

Online monitoring after the optimizations (starting 29 Sept) shows a consistent reduction in total resource loading time and time‑to‑interactive.

Performance Optimizationfrontend developmentwebpackServer-side RenderingReact NativePreactWeb Integration
Tencent Music Tech Team
Written by

Tencent Music Tech Team

Public account of Tencent Music's development team, focusing on technology sharing and communication.

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.