skip to content

React Native Web

React Native Web renders React Native components and APIs as DOM elements and atomic CSS so one codebase reaches the browser. Interviewers probe what maps cleanly and what has no web version.

on this pageshow

explore

questions

15

In a webpack build of a React Native codebase, how do you alias react-native to react-native-web, and why 'react-native$' and .web.js first?

level: middleimportance: must knowfreq 38%

answer

  1. one resolve.alias entry
  2. the dollar sign means exact match
  3. deep imports escape the alias
  4. first extension that matches wins
  5. some React Native packages ship untranspiled

basics

~10 s

Add resolve.alias { 'react-native$': 'react-native-web' } so only the exact react-native import is swapped, and list '.web.js' before '.js' in resolve.extensions so web-specific files win over shared ones.

solid answer

~40 s

`react-native-web` is installed next to `react-dom`, and webpack is told to resolve the package name to it: `resolve: { alias: { 'react-native$': 'react-native-web' } }`. The trailing `$` makes it an **exact match**, so only `import … from 'react-native'` is redirected; a deep import such as `react-native/Libraries/…` is *not* aliased and pulls React Native's own native-only source into the web build, which then fails (deep imports are also a type error under React Native 0.87's Strict TypeScript API). `resolve.extensions` lists `.web.js` before `.js` (and `.web.tsx` before `.tsx`) because webpack takes the **first extension that exists**: that order is what makes `Button.web.js` replace `Button.js` in the web bundle. Many React Native libraries publish untranspiled JSX or Flow, so their `node_modules` folders also need to go through `babel-loader`.

code

javascript · 29 lines
javascript
// web/webpack.config.js (fragment)
const path = require('path');
const appDirectory = path.resolve(__dirname, '../');

module.exports = {
  entry: path.resolve(appDirectory, 'index.web.js'),
  module: {
    rules: [
      {
        test: /\.[jt]sx?$/,
        include: [
          path.resolve(appDirectory, 'src'),
          path.resolve(appDirectory, 'node_modules/some-untranspiled-rn-lib'),
        ],
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['module:@react-native/babel-preset'],
            plugins: ['react-native-web'],
          },
        },
      },
    ],
  },
  resolve: {
    alias: { 'react-native$': 'react-native-web' },
    extensions: ['.web.tsx', '.web.ts', '.tsx', '.ts', '.web.js', '.js'],
  },
};

go deeper

for a junior

Recall that a web build maps the react-native import to react-native-web in the bundler, and that react-dom must be installed alongside it.

for a middle

Explain the exact-match alias, why deep imports escape it, and how the extension order makes .web files override shared ones.

for a senior

Cover the practical failures: untranspiled dependencies, deep imports in libraries, and a web-first extension list that includes the TypeScript variants.

for a principal

Judge whether a hand-rolled webpack setup is worth maintaining against a framework that owns the alias and resolution for every platform.

## What the alias does A React Native codebase imports its primitives from one package: `import { View, Text } from 'react-native'`. **React Native Web** (`react-native-web`, 0.21 here) exports the same names implemented with React DOM. A web build therefore does not change the application code; it changes **module resolution**, so the bundler hands the web implementation to every file that asks for `react-native`. With webpack the documented setup is a single alias: ```javascript // webpack.config.js module.exports = { resolve: { alias: { 'react-native$': 'react-native-web' }, extensions: ['.web.tsx', '.web.ts', '.tsx', '.ts', '.web.js', '.js'] } }; ``` `react-dom` must be installed too, because React Native Web renders through it. ## Why the key ends in a dollar sign In webpack's alias syntax, a trailing `$` means **exact match**. Only the bare specifier `react-native` is replaced: | Import in source | Resolves to | |---|---| | `from 'react-native'` | `react-native-web` | | `from 'react-native/Libraries/Utilities/codegenNativeComponent'` | React Native's own file, not aliased | Without the `$`, webpack would rewrite deep paths into `react-native-web/Libraries/...`, which do not exist, and the build would fail with confusing errors. With it, deep imports reach React Native's native source (Flow types, native module lookups) and fail anyway. The real fix is to have **no deep imports**: in React Native 0.87 they are already a type error under the Strict TypeScript API, and React Native Web cannot serve them. ## Why .web.js comes first `resolve.extensions` is an ordered list, and the resolver takes the **first file that exists**: 1. `import Button from './Button'` looks for `Button.web.tsx`, `Button.web.ts`, then `Button.tsx`, and so on. 2. If `Button.web.tsx` exists, it wins; the native file is never read. 3. Reverse the order and the shared `Button.tsx` would win every time, silently ignoring every web override. The same order must also include `.ts` and `.tsx` for a TypeScript codebase, each with its `.web.` variant first. ## The untranspiled-package problem React Native projects are normally bundled by Metro, which transpiles `node_modules` with React Native's Babel preset. Webpack setups usually skip `node_modules`. Many React Native libraries publish **untranspiled source** (JSX, Flow annotations, modern syntax), so a web build must: - include those specific packages in `babel-loader`'s `include` list; - use `@react-native/babel-preset` (the preset the React Native Web docs recommend) so the syntax matches what Metro would accept; - add the `react-native-web` Babel plugin for pruning unused exports. ## Diagnosing a broken alias | Symptom in the web build | Usual cause | |---|---| | Syntax error inside a `node_modules` React Native package | the package ships untranspiled JSX or Flow and is not in the loader's `include` list | | "Module not found" for a `react-native/Libraries/...` path | a deep import, which the exact-match alias deliberately leaves alone | | The web shows the native variant of a component | `.js` or `.tsx` is listed before its `.web.` counterpart | | Two copies of React Native Web in the bundle | a library depends on a different `react-native-web` version; dedupe it | | `undefined` is not a component at runtime | the import names an API React Native Web does not export | Reading the resolved path of the failing module (webpack's stats or the error's import trace) usually points straight at one of these rows. ## Checklist for a first web build - `react-dom` and `react-native-web` installed at versions matching the app's React. - `'react-native$'` alias and web-first extension order. - `babel-loader` covering app source and untranspiled dependencies. - An image loader for static assets imported with `require`. - A web entry (for example `index.web.js`) that registers the app with `AppRegistry` and runs it into a DOM element. - HTML root styles that give the app full height, since a root `View` with `flex: 1` needs a sized parent. ## Where this sits among the alternatives The same idea appears in every toolchain: Babel's module-resolver can do the alias at compile time, Jest can map it with `moduleNameMapper`, Node can do it with a module-alias package for server rendering, and Expo does it inside its Metro configuration so an Expo app needs no alias at all.

  • With webpack aliasing 'react-native$' to react-native-web, what happens to import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'?
    It is not aliased, because the `$` restricts the alias to the exact specifier. Webpack resolves React Native's own file, which is Flow-typed, native-only code, so the web build fails or the module breaks at runtime. The fix is to keep such imports in native-only files (`.native.tsx`) so the web bundle never reaches them.
  • In a webpack setup for React Native Web, why must some node_modules packages be passed through babel-loader?
    Because many React Native libraries publish untranspiled JSX, Flow or modern syntax, relying on Metro to transpile them. Webpack configurations usually exclude `node_modules`, so those packages fail to parse. Add each one to the loader's `include` list and use `@react-native/babel-preset` so it is compiled as Metro would.

saying these in an interview costs you the question

  • Without the $ the alias is safer because it also covers deep imports.
  • The order of resolve.extensions does not matter; webpack picks the most specific file.
  • The alias requires changing every import to react-native-web in source.
  • Deep imports from react-native/Libraries work on the web once the alias is set.
  • react-dom is optional because react-native-web renders by itself.
open as a page

How does React Native Web turn StyleSheet.create styles into CSS, and what does the browser receive for a plain inline style object?

level: middleimportance: must knowfreq 40%

basics

~20 s

React Native Web compiles each StyleSheet.create declaration into atomic CSS: one deduplicated class per property-value pair, inserted once into a shared style sheet. A plain object that never went through create becomes an inline style attribute.

open as a page

In React Native Web, what happens when shared code calls an API with no web implementation, such as Alert.alert, findNodeHandle or a native module?

level: middleimportance: must knowfreq 38%

basics

~20 s

It depends on the API. React Native Web throws for some (findNodeHandle), silently mocks others (Alert.alert does nothing, StatusBar renders null, NativeModules holds only UIManager) and lacks platform-only exports such as PermissionsAndroid, which arrive as undefined.

open as a page

To serve an Expo app's sign-up flow in a browser, what must be installed, and how does Expo alias react-native to react-native-web on Metro?

level: juniorimportance: should knowfreq 38%

basics

~20 s

Install react-dom, react-native-web and @expo/metro-runtime with npx expo install, then run npx expo start --web. Expo CLI's Metro resolver aliases react-native to react-native-web for the web platform, so no webpack or Babel alias is needed.

open as a page

In React Native Web, which DOM elements do View and Text render, and why does a nested Text become a span?

level: juniorimportance: should knowfreq 42%

basics

~20 s

React Native Web renders View as a div carrying a flexbox reset (column direction, no shrink) and Text as a div at the top level but a span inside another Text, so nested runs stay inline and inherit font and color.

open as a page

For a React Native Web build, how does a Babel alias to react-native-web differ from a bundler alias, and what does babel-plugin-react-native-web add?

level: middleimportance: should knowfreq 24%

basics

~20 s

A Babel alias rewrites the import string during compilation, so any tool that runs Babel sees react-native-web. babel-plugin-react-native-web also rewrites each named import to its own module path, so unused components are dropped from the bundle.

open as a page

In React Native Web, what DOM does a Pressable with role="button" produce, and which browser events fire its onPress?

level: middleimportance: should knowfreq 34%

basics

~20 s

React Native Web renders a Pressable with role="button" as a real button element with type="button" and tabIndex 0. onPress fires from the browser's click event, so mouse clicks, taps and Enter or Space all trigger it.

open as a page

In React Native Web, what do href and hrefAttrs render on a View or Text, and why prefer them over Linking.openURL in onPress?

level: middleimportance: should knowfreq 26%

basics

~20 s

In React Native Web, href on View or Text renders an a element and hrefAttrs adds download, rel and target. A real anchor keeps new-tab gestures, copy link, crawlability and link semantics that onPress with Linking.openURL loses.

open as a page

With React Native Web, what does Platform report in a browser, and when is a .web.tsx file better than an inline Platform.OS check?

level: middleimportance: should knowfreq 36%

basics

~10 s

In React Native Web, Platform.OS is 'web' and Platform.select picks the web key, then default. Prefer a .web.tsx file when the native version imports a native-only package: an inline branch still evaluates that import.

open as a page

An Expo app's scan screen imports a native-only camera module with no web build, and the web bundle crashes on load; how do you ship a web version?

level: seniorimportance: should knowfreq 22%

basics

~20 s

Move the native-only import behind a platform file so the web bundle never loads it, then give ScanScreen.web.tsx a browser path: a camera library with a web implementation, a file input with capture, or a clear 'use the app' fallback.

open as a page

For a React Native product adding a web version, how do you decide between sharing the UI through React Native Web and building a separate web app?

level: principalimportance: should knowfreq 30%

basics

~10 s

Share UI through React Native Web when the web product is app-like and most screens use portable components; build web UI separately when it is content-led, SEO-led or native-feature-heavy, and share logic either way.

open as a page

In React Native Web, what DOM does an Image render, and how do resizeMode and its accessibility label map onto it?

level: middleimportance: nice to knowfreq 18%

basics

~20 s

React Native Web renders Image as a div holding a background-image layer sized by resizeMode, plus an invisible img whose alt comes from aria-label or accessibilityLabel, so the context menu and assistive technology still see an image.

open as a page

Which React Native components render but behave differently under React Native Web, and why can web-tested code still fail on React Native 0.87?

level: middleimportance: nice to knowfreq 18%

basics

~20 s

Some components are stubs on the web: TouchableNativeFeedback is an unimplemented view, RefreshControl a plain View, KeyboardAvoidingView a mock. React Native Web also still exports InteractionManager, which React Native 0.87 removed, so web-only testing hides native failures.

open as a page

When server-rendering a React Native Web sign-up page, how do AppRegistry.getApplication and getStyleElement produce HTML with styles, and why collect styles after rendering?

level: seniorimportance: nice to knowfreq 15%

basics

~10 s

AppRegistry.getApplication returns the app element and getStyleElement. Render the element to HTML first, then call getStyleElement, which reads React Native Web's style sheet at that moment, so it contains every rule the render needed.

open as a page

Under React Native Web, a PanResponder swipe row works on phones but drops gestures on desktop; how are responder events produced in a browser?

level: seniorimportance: nice to knowfreq 14%

basics

~20 s

React Native Web reimplements the responder system with document-level mouse and touch listeners, treating a mouse as one emulated touch. Context menus, window blur, native drags, text selection and ancestor scrolls terminate the responder, which is where desktop gestures usually die.

open as a page