•
6 September 2025
•
7 mins
This post documents how I imported a React Native component library into a Next.js pages‑router app and rendered it on the web using React Native Web, @expo/next-adapter, and Babel. This configuration is a viable option for preventing duplicate code across a mobile + NextJS app codebase.
Caveat: Our team at Voyage Travel Technologies PLC eventually decided on removing this. Using React Native components with NextJS resulted in the following problems for us:
next build to work@expo/next-adapterreact-native-css-interop.babelrc (Next disables SWC when a custom Babel config is present)@isaacaddis/private-rn-library), RN primitives (@rn-primitives/*)globals.css use @import "tailwindcss"; (not v3’s @tailwind base; ...).className without CSS interop. Map the primitives you actually render.Pressable under the hood — interop it specifically.transpilePackages or you’ll hit syntax/runtime errors.important: "html" so Tailwind wins over other stylesheets.@isaacaddis/private-rn-library).transpilePackages so Next transpiles it for the browser.content so utility classes used inside it are generated.View, Text, TouchableOpacity, and especially Pressable for triggers/buttons).After these steps, components can be imported and used as follows:
import { Card, Dialog, DialogTrigger } from "@isaacaddis/private-rn-library";
import { Text } from "react-native";
export default function Page() {
return (
<div className="p-6">
<Card className="rounded-xl border p-4">
<Text>Card content</Text>
</Card>
<Dialog>
<DialogTrigger className="bg-blue-600 p-3 rounded">
<Text className="text-white">Open</Text>
</DialogTrigger>
</Dialog>
</div>
);
}
import { withExpo } from "@expo/next-adapter";
/** @type {import('next').NextConfig} */
const nextConfig = withExpo({
reactStrictMode: true,
transpilePackages: [
"react-native",
"react-native-web",
"nativewind",
"react-native-css-interop",
"@rn-primitives",
"@isaacaddis/private-rn-library",
"react-native-reanimated",
],
webpack: (config) => {
config.resolve.alias = {
...(config.resolve.alias || {}),
"react-native$": "react-native-web",
"phosphor-react-native": "phosphor-react",
};
return config;
},
});
export default nextConfig;
jsxImportSource line){
"compilerOptions": {
"jsxImportSource": "nativewind",
"jsx": "preserve",
"moduleResolution": "bundler",
"strict": true
},
"include": ["next-env.d.ts", "nativewind-env.d.ts", "**/*.ts", "**/*.tsx"]
}
{
"presets": ["next/babel", "@babel/preset-env", "@babel/preset-flow"],
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"runtime": "automatic",
"importSource": "nativewind"
}
]
]
}
/// <reference types="nativewind/types" />
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./pages/**/*.{ts,tsx,js,jsx}",
"./components/**/*.{ts,tsx,js,jsx}",
"./node_modules/@isaacaddis/private-rn-library/**/*.{ts,tsx,js,jsx}",
],
presets: [require("nativewind/preset")],
important: "html",
theme: {
extend: {
// tokens (colors, sizes, etc.)
},
},
plugins: [],
};
const config = {
plugins: ["@tailwindcss/postcss"],
};
export default config;
@import "tailwindcss";
import "@/styles/globals.css";
import type { AppProps } from "next/app";
import { cssInterop } from "react-native-css-interop";
import { View, Text, TouchableOpacity, Pressable } from "react-native";
// Map className -> style for primitives actually rendered in your app/libs
cssInterop(View, { className: "style" });
cssInterop(Text, { className: "style" });
cssInterop(TouchableOpacity, { className: "style" });
cssInterop(Pressable, { className: "style" }); // critical for many Trigger components
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
@import "tailwindcss"; in globals.css.border-red-500 doesn’t
Pressable without CSS interop.cssInterop(Pressable, { className: "style" }).transpilePackages and ensure the library ships browser‑compatible JS.important: "html" to Tailwind config to increase specificity.Using @expo/next-adapter with React Native Web, Tailwind v4, NativeWind, react-native-css-interop, and Babel allows for importing a React Native library inside a Next.js web app without duplicating UI code. The required steps are: transpile React Native and the library, use Tailwind v4’s @import CSS, include the library paths in Tailwind content, and map React Native primitives (including Pressable) with CSS interop so className resolves to styles.
Edit (Oct 25, 2025): Added a caveat to the introduction.
Edit (Nov 8, 2025): Updated the caveat for accuracy.
Like it? Share it!