惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cyberwarzone
Cyberwarzone
C
CXSECURITY Database RSS Feed - CXSecurity.com
C
CERT Recently Published Vulnerability Notes
The Hacker News
The Hacker News
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Security Archives - TechRepublic
Security Archives - TechRepublic
Google Online Security Blog
Google Online Security Blog
D
Docker
H
Hacker News: Front Page
Recent Announcements
Recent Announcements
GbyAI
GbyAI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
P
Proofpoint News Feed
Security Latest
Security Latest
AI
AI
I
InfoQ
Google DeepMind News
Google DeepMind News
阮一峰的网络日志
阮一峰的网络日志
S
Secure Thoughts
Attack and Defense Labs
Attack and Defense Labs
P
Proofpoint News Feed
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Scott Helme
Scott Helme
N
News | PayPal Newsroom
Y
Y Combinator Blog
V
Visual Studio Blog
Latest news
Latest news
大猫的无限游戏
大猫的无限游戏
Microsoft Security Blog
Microsoft Security Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
WordPress大学
WordPress大学
V
Vulnerabilities – Threatpost
C
Cyber Attacks, Cyber Crime and Cyber Security
TaoSecurity Blog
TaoSecurity Blog
S
Security @ Cisco Blogs
D
DataBreaches.Net
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
N
Netflix TechBlog - Medium
T
Troy Hunt's Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
H
Heimdal Security Blog
美团技术团队
S
Securelist
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
S
Security Affairs
T
Threat Research - Cisco Blogs
Webroot Blog
Webroot Blog
G
Google Developers Blog
aimingoo的专栏
aimingoo的专栏

Nx Blog

Sharing Tailwind CSS Styles Across Apps in a Monorepo | Nx Blog How SiriusXM Stays Competitive by Iterating and Getting to Market Fast | Nx Blog Agentic Experience Is the New Developer Experience | Nx Blog Nx Joins the Linux Foundation and the Agentic AI Foundation | Nx Blog A Monorepo Is NOT a Monolith | Nx Blog Why we deleted (most of) our MCP tools | Nx Blog Teach Your AI Agent How to Work in a Monorepo | Nx Blog How Broadcom stays efficient and nimble with monorepos | Nx Blog Why Monorepos are King in the Age of AI | Nx Blog Nx 2026 Roadmap: Expanding Agent Autonomy, Improving Performance, Better Polyglot and More | Nx Blog End to End Autonomous AI Agent Workflows with Nx | Nx Blog Autonomous Agents at Scale | Nx Blog Scaling 700+ Projects: How Nx Became a 'No-Brainer' for Caseware | Nx Blog Configure Tailwind v4 with Angular in an Nx Monorepo | Nx Blog The Missing Multiplier for AI Agent Productivity | Nx Blog A Year of Nx Webinars | Nx Blog Wrapping Up 2025 | Nx Blog Nx 22.3 Release: Angular 21 Support, tsgo Compiler, and Prettier v3 | Nx Blog Nx Cloud Release: Agent Resource Usage | Nx Blog Nx Platform Outperforms DIY Cache by 5x | Nx Blog An Nx Carol: Past, Present, and Future of Your Monorepo | Nx Blog Nx 22.1 Release: Terminal UI on Windows, Storybook 10, Vitest 4, and more! | Nx Blog The Compounding Effect: How Nx Features Multiply Performance Gains | Nx Blog 10 Monorepo Myths Debunked: Separating Fact from Fiction | Nx Blog Nx Cloud Release: Enterprise Task Analytics | Nx Blog Watch and Rebuild Storybook Dependencies with Nx | Nx Blog Book - React for Enterprise: Timeless Architecture for Enterprise Apps | Nx Blog Beyond Remote Cache: Unlock 70% More CI Performance | Nx Blog Nx 22 Release: Expanding the build platform | Nx Blog What's the Point of Generating All This Code If You Can't Merge It? | Nx Blog What's New in Nx Self-Healing CI | Nx Blog Nx Highlights: Smarter AI integration, all-new graph UI, and big new versions of your favorite tools | Nx Blog Making the Case for Smarter Monorepos, and How to Not Get Fooled by Myths | Nx Blog Integrating Biome in 20 Minutes | Nx Blog S1ngularity - What Happened, How We Responded, What We Learned | Nx Blog Stop Babysitting Your PRs: Self-Healing CI Cuts Time to Green by 50% | Nx Blog UKG Unifies Their Codebase and Eliminates CI Overhead to Focus on Customer Value | Nx Blog How Git Worktrees Changed My AI Agent Workflow | Nx Blog Nx Cloud Workspace Graph: See Your Organization's Code Structure Like Never Before | Nx Blog Seamless Java Deployment in Nx Using Docker | Nx Blog Getting Mobile Into Your Monorepo: Android + Nx | Nx Blog Polyglot Projects Made Easy: Integrating Spring Boot into an Nx Workspace | Nx Blog The Journey of the Nx Plugin for Gradle: From Prototype to Production | Nx Blog Combining Predictability and Intelligence With Nx Generators and AI | Nx Blog A New UI For The Humble Terminal | Nx Blog Continuous tasks are a huge DX improvement | Nx Blog New and Improved Module Federation Experience with Nx | Nx Blog A New UI for Nx Migration | Nx Blog Custom Task Runners and Self-Hosted Caching Changes | Nx Blog Enterprise Angular Monorepo Patterns | Nx Blog Using Rspack with Angular | Nx Blog Angular Architecture Guide To Building Maintainable Applications at Scale | Nx Blog Modern Angular Testing with Nx | Nx Blog Nx Update: 20.5 | Nx Blog Are Monorepos the Answer to Better AI-Assisted Development? | Nx Blog Making Cursor Smarter with an MCP Server For Nx Monorepos | Nx Blog React Development for 2025 | Nx Blog Using Apollo GraphQL in an Nx Workspace | Nx Blog Angular State Management for 2025 | Nx Blog Tailoring Nx for Your Organization | Nx Blog Nx Cloud Pipelines Come To Nx Console | Nx Blog Define the relationship with monorepos | Nx Blog See your affected project graph in Nx Cloud | Nx Blog Handling CORS In Your Workspace | Nx Blog Improve your architecture and CI pipeline times with Nx projects | Nx Blog Announcing Nx 20 | Nx Blog Introducing Nx Powerpack | Nx Blog Nx 19.5 is here! Stackblitz, Bun, Incremental Builds for Vite, Gradle Test Atomizer | Nx Blog Introducing Explain with AI | Nx Blog Nx Enterprise Podcast Episode 2: Tine Kondo | Nx Blog Monorepos and CI can be a Mess - Here's How Nx and Nx Cloud Fixed It | Nx Blog Nx Enterprise Podcast Episode 1: Hicham El Hammouchi | Nx Blog Nx 19.0 Release!! | Nx Blog Manage Your Gradle Project using Nx | Nx Blog Making the Argument for Monorepos | Nx Blog Reliable CI. A new execution model fixing both flakiness and slowness | Nx Blog Monorepos - Why Speed Matters | Nx Blog Nx Agents Walkthrough: Effortlessly Fast CI Built for Monorepos | Nx Blog Launch Nx Week Recap | Nx Blog Versioning and Releasing Packages in a Monorepo | Nx Blog Fast, Effortless CI | Nx Blog Introducing @nx/nuxt Enhanced Nuxt.js Support in Nx | Nx Blog What if Nx Plugins Were More Like VSCode Extensions | Nx Blog Monorepos: the Benefits, Challenges, and Importance of Tooling Support | Nx Blog Nx — Highlights of 2023 | Nx Blog Nx 17.2 Update | Nx Blog Unit Testing Expo Apps With Jest | Nx Blog Nx Docs AI Assistant | Nx Blog State Management Nx React Native/Expo Apps with TanStack Query and Redux | Nx Blog Nx 17 has Landed | Nx Blog Nx Conf 2023 — Recap | Nx Blog Nx Raises $16M Series A | Nx Blog Introducing Playwright Support for Nx | Nx Blog Nx 16.8 Release!!! | Nx Blog Step-by-Step Guide to Creating an Expo Monorepo with Nx | Nx Blog Qwikify your Development with Nx | Nx Blog Create Your Own create-react-app CLI | Nx Blog Storybook Interaction Tests in Nx | Nx Blog Evergreen Tooling — More than Just CodeMods | Nx Blog A Practical Guide on Effective AI Use - AI as Your Peer Programmer | Nx Blog
Share code between React Web & React Native Mobile with Nx | Nx Blog
Emily Xiong · 2022-02-02 · via Nx Blog

A problem I try to solve: I got this awesome idea, not only do I want to create a web app, but I also want to create a mobile app for it. Usually creating web and mobile apps require totally different tech stacks, and it is pretty hard to share code. This article shows how I added a React web app and a React Native mobile app in the same monorepo using Nx, and how I optimized codeshare between the two.

I am mostly a web developer, so let's start with the web app first: https://xiongemi.github.io/studio-ghibli-search-engine. It is a search engine for movies and characters under Studio Ghibli:

Screenshot of web app

Example Repo: xiongemi/studio-ghibli-search-engine

Github page: https://xiongemi.github.io/studio-ghibli-search-engine

Now let's create the corresponding mobile version of this app.

Tech Stack

Currently, there's only a React web app within our Nx workspace. If I run nx graph, the dependency graph looks like the below:

Dependency graph

React Native Setup

To get started we need to add React Native support to our Nx workspace:

# npm
npm install @nrwl/react-native --save-dev# yarn
yarn add @nrwl/react-native --dev

Next, we can generate a new React Native app by running:

npx nx generate @nrwl/react-native:app studio-ghibli-search-engine-mobile

Note, if you're using VSCode you might want to try Nx Console for a more visual experience of running such commands.

As a result of running the above command, you should now have two new folders under the apps directory: studio-ghibli-search-engine-mobile and studio-ghibli-search-engine-mobile-e2e

studio-ghibli-search-engine-mobile created under apps

If we now run nx dep-graph again, the dependency graph looks like this:

Dependency graph

Note that there is no code shared between studio-ghibli-search-engine-mobile and studio-ghibli-search-engine-web. However, our goal is to reuse some of the functionality that we have previously written for the web version on our new React native version of the app.

Even though our goal is to share as much as possible between our React web app and the React Native app, there are parts that simply cannot be shared.

UI

We have to rewrite all the UI components for the mobile app. Unlike Cordova or Ionic, React Native is NOT a webview. The JavaScript we wrote got interpreted and converted to mobile native elements. Hence we cannot simply reuse UI HTML elements written for the React web app.

Here's a quick list of libraries we've used for the React web app and a corresponding React Native counterpart library we can use.

Routing

Material Design Library

Besides the above React Native libraries, there are some core utility libraries that need to be installed:

  • react-native-reanimated
  • react-native-gesture-handler
  • react-native-screens
  • react-native-safe-area-context
  • @react-native-community/masked-view
  • react-native-vector-icons

The corresponding install command would be:

# npm
npm install @react-navigation/native @react-navigation/native-stack react-native-paper react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view --save# yarn
yarn add @react-navigation/native @react-navigation/native-stack react-native-paper react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view

Storage

For the React Web app, we use redux-persist, which persists the redux store in localstorage. However, localstorage is not supported by React Native.

For the web, the variable persistConfig passed to persistStore from redux-persist is:

import storage from 'redux-persist/lib/storage';
const persistConfig = {
  key: 'root',
  storage: storage,
  whitelist: ['search', 'films', 'people'],
  transforms: [transformEntityStateToPersist],
};

However, for the mobile, we need to install the library @react-native-async-storage/async-storage:

# npm
npm install @react-native-async-storage/async-storage --save-dev# yarn
yarn add @react-native-async-storage/async-storage --dev

As a result, the persistConfig passed to persistStore from redux-persist becomes:

import AsyncStorage from '@react-native-async-storage/async-storage';
const persistConfig = {
  key: 'root',
  storage: AsyncStorage,
  whitelist: ['search', 'films', 'people'],
  transforms: [transformEntityStateToPersist],
};

History

On the React web app, we use connected-react-router to put the router state into the Redux store. However, the History API (windows.history) is not supported by React Native. As an alternative, we can use createMemoryHistory.

For the web app, the history is:

import { createHashHistory, History } from 'history';
const history: History = createHashHistory();

For the mobile app, the history is:

import { createMemoryHistory, History } from 'history';
const history: History = createMemoryHistory();

To make our code more re-usable we could slightly refactor the creation of the root reducer with connected-react-router, such that it takes the history object as an argument:

import { combineReducers } from '@reduxjs/toolkit';
import { connectRouter } from 'connected-react-router';
import { History } from 'history';
import { filmsSlice } from '../films/films.slice';
import { peopleSlice } from '../people/people.slice';
import { searchSlice } from '../search/search.slice';
import { RootState } from './root-state.interface';
export const createRootReducer = (history: History) =>
  combineReducers<RootState>({
    films: filmsSlice.reducer,
    router: connectRouter(history) as any,
    search: searchSlice.reducer,
    people: peopleSlice.reducer,
  });

Query Parameters

When you develop on the web, the easiest way to pass ahead state or information, in general, is to leverage the URL query parameters. In our search app example, we can simply have something like ?search=searchText.

We can use react-router-dom to push a new history entry.

import { useHistory } from 'react-router-dom';
const history = useHistory();
const submitSearchForm = (text: string) => {
  history.push(`${AppRoutes.results}?search=${text}`);
};

To read and parse the current query parameter search:

import { useLocation } from 'react-router-dom';
const params = new URLSearchParams(useLocation().search);
const searchParam = params.get('search');

Although the mobile app URLs are not visible, we can still pass parameters. Note that we have to use a different package @react-navigation/native though.

import { useNavigation } from '@react-navigation/native';
const navigation = useNavigation();
const submitSearchForm = () => {
  navigation.navigate(AppRoutes.results, { search: text });
};

To read and parse the parameter:

import { RouteProp, useRoute } from '@react-navigation/native';
const route = useRoute<RouteProp<{ params: { search: string } }>>();
const searchParam = route.params?.search;

To type checking with typescript for react-navigation, we need to create a type RootStackParamList for mappings of route name to the params of the route:

export type RootStackParamList = {
  [AppRoutes.search]: undefined;
  [AppRoutes.results]: { search: string };
};

We also need to specify a global type for your root navigator:

declare global {
  // eslint-disable-next-line @typescript-eslint/no-namespace
  namespace ReactNavigation {
    // eslint-disable-next-line @typescript-eslint/no-empty-interface
    interface RootParamList extends RootStackParamList {}
  }
}

So we create the stack navigator, we need to pass the above RootStackParamList type:

import { createNativeStackNavigator } from '@react-navigation/native-stack';
const Stack = createNativeStackNavigator<**RootStackParamList**>();

Environment Variables

Nx comes with a set of different options for handling environment variables. In our workspace, we have a simple .env file at the workspace root:

NX_REQUEST_BASE_URL=://ghibliapi.herokuapp.com

This works nicely for our React web build, but it doesn't for our React Native application. This is because React Native and React apps use different Javascript bundlers. React Native uses Metro and React uses Webpack. Therefore, when we try to access process.env.NX_REQUEST_BASE_URL, we get undefined.

To solve this, we can use the react-native-config library

# npm
npm install react-native-config --save-dev# yarn
yarn add react-native-config --dev

Here's an example of how to set up react-native-config: https://github.com/luggit/react-native-config#setup.

After that, we can have a simple utility function to retrieve the environment variables in our app.

import Config from 'react-native-config';
export function getEnv(envName: string) {
  return process.env[envName] || Config[envName];
}

To access the environment variable NX_REQUEST_BASE_URL, we can then simply use the above function:getEnv('NX_REQUEST_BASE_URL').

Fetch With HTTP

On the web, you most probably lean on the fetch API to make network requests. On iOS, however, you'll get an error saying: TypeError: Network request failed.

It turns out that React Native does not allow HTTP requests by default: https://stackoverflow.com/questions/38418998/react-native-fetch-network-request-failed.

To fix this, for iOS, open apps/studio-ghibli-search-engine-mobile/ios/StudioGhibliSearchEngineApp/Info.plist and add the request URL to NSExceptionDomains under NSAppTransportSecurity:

<key>NSAppTransportSecurity</key>
 <dict>
  <key>NSExceptionDomains</key>
  <dict>
   <key>localhost</key>
   <dict>
    <key>NSExceptionAllowsInsecureHTTPLoads</key>
    <true/>
   </dict>
   <key>ghibliapi.herokuapp.com</key>
   <dict>
    <key>NSExceptionAllowsInsecureHTTPLoads</key>
    <true/>
   </dict>
  </dict>
 </dict>

Similarly, for Android, open apps/studio-ghibli-search-engine-mobile/android/app/src/main/res/xml/network_security_config.xml, and add the request URL to this config file:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">10.0.2.2</domain>
        <domain includeSubdomains="true">localhost</domain>
        <domain includeSubdomains="true">herokuapp.com</domain>
    </domain-config>
</network-security-config>

This should get rid of the network error.

It seems like there are quite a few customizations that need to be done for React Native apps. However, the majority of non-UI code could be reused.

All the business logic code that is not UI could be shared. For this example, I got 3 libraries in my monorepo and all of them could be shared:

  • models: types and interface definitions
  • services: services that interact with API
  • store: redux store

With Nx, it requires zero configuration to share the above library code. Even though when I created these libraries for a web app, I used commands like nx generate @nrwl/react:lib store, I could still use them directly in my react native mobile app.

For example, I need to create a film page to display film details with film id passed in as a parameter:

Screenshot of Film Page on Mobile (left: iOS, right: Android)

I would do import from the store library directly:

import {
  filmsActions,
  filmsSelectors,
  RootState,
} from '@studio-ghibli-search-engine/store';

The film component would become:

import { AnyAction, ThunkDispatch } from '@reduxjs/toolkit';
import {
  filmsActions,
  filmsSelectors,
  RootState,
} from '@studio-ghibli-search-engine/store';

const mapStateToProps = (state: RootState) => {
  return {
    getFilm: (id: string) => filmsSelectors.selectFilmById(id)(state),
  };
};

const mapDispatchToProps = (
  dispatch: ThunkDispatch<RootState, void, AnyAction>
) => {
  return {
    fetchFilms() {
      dispatch(filmsActions.fetchFilms());
    },
  };
};

type mapStateToPropsType = ReturnType<typeof mapStateToProps>;
type mapDispatchToPropsType = ReturnType<typeof mapDispatchToProps>;

type FilmProps = mapStateToPropsType & mapDispatchToPropsType;

export { mapStateToProps, mapDispatchToProps };
export type { FilmProps };
import { RouteProp, useRoute } from '@react-navigation/native';
import { FilmEntity } from '@studio-ghibli-search-engine/models';
import { getEnv } from '@studio-ghibli-search-engine/services';
import React, { useEffect, useState } from 'react';
import { SafeAreaView, ScrollView, Image, View } from 'react-native';
import {
  Button,
  Divider,
  Headline,
  Paragraph,
  Subheading,
  Title,
} from 'react-native-paper';
import { styles } from 'react-native-style-tachyons';
import { connect } from 'react-redux';

import Loading from '../shared/loading/loading';
import { useLink } from '../shared/open-link/open-link';

import { FilmProps, mapDispatchToProps, mapStateToProps } from './film.props';

export function Film({ getFilm, fetchFilms }: FilmProps) {
  const [film, setFilm] = useState<FilmEntity>();

  const route = useRoute<RouteProp<{ params: { id: string } }>>();
  const id = route.params?.id;

  const openHboMax = useLink(getEnv('NX_HBO_STREAMING_URL'), 'HBO Max');
  const openNetflix = useLink(getEnv('NX_NETFLIX_STREAMING_URL'), 'Netflix');

  useEffect(() => {
    fetchFilms();
  }, [fetchFilms]);

  useEffect(() => {
    setFilm(getFilm(id));
  }, [id, getFilm]);

  return film ? (
    <SafeAreaView>
      <ScrollView contentInsetAdjustmentBehavior="automatic">
        <View style={[styles.pa3]}>
          <Image
            style={{ height: 200, width: '100%', resizeMode: 'contain' }}
            source={{ uri: film.movieBanner }}
          />
          <Headline>{film.title}</Headline>
          <Subheading>
            {film.originalTitle} / {film.originalTitleRomanised}
          </Subheading>
          <Paragraph>Release: {film.releaseDate}</Paragraph>
          <Paragraph>Director: {film.director}</Paragraph>
          <Paragraph>Producer: {film.producer}</Paragraph>
          <Paragraph>Running Time: {film.runningTime} minutes</Paragraph>
          <Paragraph>Rotten Tomatoes Score: {film.rtScore}</Paragraph>

          <Divider />

          <Title>Plot</Title>
          <Paragraph>{film.description}</Paragraph>

          <Divider />

          <Button onPress={openHboMax}>Watch on HBO Max</Button>
          <Button onPress={openNetflix}>Watch on Netflix</Button>
        </View>
      </ScrollView>
    </SafeAreaView>
  ) : (
    <Loading />
  );
}

export default connect(mapStateToProps, mapDispatchToProps)(Film);

Note I could import from @studio-ghibli-search-engine/models, @studio-ghibli-search-engine/services and @studio-ghibli-search-engine/store directly.

Now when I run nx dep-graph, it shows the dependency graph below where all these 3 libraries are shared between web and mobile:

Dependency graph

For this example project, to create the mobile app, it took me some time to rewrite the entire UI. However, I do not need to make any changes to the above libraries.

Screenshots of Mobile App (left: iOS, right: Android)

Conclusion

In this article, we ended up building both, a React-based web application and a corresponding React Native app in the same repository using Nx.

Nx's architecture promotes the separation of concerns, splitting things into apps (which are technology-specific) and libs which can be technology-specific or technology-independent. That allows us to easily have our common business logic in a technology-independent library which in turn (thanks to Nx's setup) be easily linked to both, our React web and React Native mobile app.

Although there are UI-specific differences we need to account for, that simply comes with one being a web tech stack and the other being a native app, we were still able to share big chunks of the technology-independent business logic of our application. That ultimately helps with maintenance and having feature parity across different platforms.

(Note, the repository with the code for this article is linked at the very top)