nativewind icon indicating copy to clipboard operation
nativewind copied to clipboard

NativeWind Styling Not Working in NX Monorepo with Expo

Open AryanFrobada opened this issue 11 months ago • 17 comments

I'm setting up a NX monorepo with Expo for mobile apps and using NativeWind for styling. While the project runs fine with standard CSS, I'm encountering issues when integrating NativeWind.
Despite following the official documentation for NativeWind setup with Expo Router (Link: https://www.nativewind.dev/getting-started/expo-router), the styles do not appear on the screen.

My environment setup includes creating the NX Monorepo, installing expo by configuring it successfully and integrating nativewind using the official documentation.
Below is my tailwind.config.js file

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./app/**/*.{js,jsx,ts,tsx}",
    "./src/**/*.{js,jsx,ts,tsx}", // Assuming components are in the src folder
  ],
  presets: [require("nativewind/preset")],
  theme: {
    extend: {},
  },
  plugins: [],
};

Below is a sample component where styles are not showing up:

import React from 'react';
import { Text, View } from 'react-native';
import "../../global.css";

export default function App() {
  return (
    <View>
      <Text className='flex flex-col justify-center items-center bg-black text-white'>
        Hello World
      </Text>
    </View>
  );
}

I tried to debug about this by clearing the cache - expo start -c , ensured tailwind.config.js included all relevant paths and verifying the NativeWind setup with the documentation.

While researching, I found this GitHub issue (https://github.com/nativewind/nativewind/issues/627) suggesting updates to the metro.config.js file. After applying the changes, the project fails to load and shows the following error: "Development server returned response error code: 500" on the emulator.

I've attached the error screenshot for reference (see below).
nxNativewinderror


I’ve created a GitHub repository to demonstrate the issue: https://github.com/AryanFrobada/nx-monorepo-expo-with-nativewind-setup
Has anyone successfully integrated NativeWind into an NX monorepo with Expo?
I’d appreciate it if someone could review my setup and let me know if I’ve missed something, especially with configurations like the Metro bundler or Tailwind paths.

I’d also like guidance on resolving the status code 500 error that appears after updating the metro.config.js file based on this suggestion - https://github.com/nativewind/nativewind/issues/627

I’d be grateful for any advice or suggestions to resolve this issue. Thank you for your time and support!

AryanFrobada avatar Jan 09 '25 06:01 AryanFrobada

I’m also facing the same issue with NativeWind styling not showing up in my NX monorepo with Expo. Like you, I followed the official documentation for setting up NativeWind with Expo Router, but the styles don’t seem to appear on the screen.

rapuruprudhvi avatar Jan 09 '25 06:01 rapuruprudhvi

I’m also facing the same issue while integrating NativeWind into my NX monorepo with Expo. Despite following the documentation, I’ve tried modifying metro.config.js as suggested in GitHub issue (#627 ), but I ended up with a "Development server returned response error code: 500" on the emulator. It seems like something’s missing or misconfigured, but I haven’t been able to resolve it yet. Any help or suggestions would be appreciated! Here's the GitHub repository: https://github.com/naveenm666/nx-expo-nativewind

naveenm666 avatar Jan 09 '25 07:01 naveenm666

I'm encountering the same problem with NativeWind styles not showing in my NX monorepo while using Expo. Like you, I followed the official NativeWind documentation for integration with Expo Router, but the styles aren't rendering on the screen. Could you suggest an alternative solution or approach to resolve this?

vallepu-kiran avatar Jan 09 '25 09:01 vallepu-kiran

Have kinda same issue; no errors after following this which got things working (I thought, at least no errors).

However, applying classNames to any component has no affect. Also tried installing nativecn components, no errors but again - they have no styling at all.

  • I'm running the verifyInstallation() which yields NativeWind verifyInstallation() found no errors.
  • I've cleared cache upon starting expo
  • global.css is present and loads tailwind correctly after following above link example

Package versions;

  • "nativewind": "^4.1.23"
  • "tailwindcss": "^3.4.17"

Using pnpm

metro.config.js According to link above as to how to load configs within when using NX monorepo.

const { withNxMetro } = require('@nx/expo');
const { getDefaultConfig } = require('@expo/metro-config');
const { mergeConfig } = require('metro-config');

const { withNativeWind } = require("nativewind/metro")

const defaultConfig = getDefaultConfig(__dirname);
const { assetExts, sourceExts } = defaultConfig.resolver;

/**
 * Metro configuration
 * https://reactnative.dev/docs/metro
 *
 * @type {import('metro-config').MetroConfig}
 */
const customConfig = {
  cacheVersion: 'expo-mobile-app',
  transformer: {
    babelTransformerPath: require.resolve('react-native-svg-transformer'),
  },
  resolver: {
    assetExts: assetExts.filter((ext) => ext !== 'svg'),
    sourceExts: [...sourceExts, 'cjs', 'mjs', 'svg'],
  },
};

async function createConfig() {
  const nxConfig = await withNxMetro(mergeConfig(defaultConfig, customConfig), {
    debug: false,
    extensions: [],
    watchFolders: []
  })

  return withNativeWind(nxConfig, {
    input: './src/styles/global.css'
  })
}

module.exports = createConfig()

babel.config.js

module.exports = function (api) {
  api.cache(true)
  return {
    presets: [
      ["babel-preset-expo", { jsxImportSource: "nativewind" }],
      "nativewind/babel",
    ],
    plugins: [
      "react-native-reanimated/plugin",
    ],
  }
}

Output when DEBUG (all green);

  • nativewind input: [*****]/src/styles/global.css +0ms
  • nativewind important: undefined +49ms
  • nativewind checking TypeScript setup +0ms
  • nativewind withCssInterop +0ms
  • nativewind outputDirectory [******]/node_modules/.pnpm/[email protected]_aoovcboblia2ohvrwqeki57kii/node_modules/react-native-css-interop/.cache +0ms
  • nativewind isRadonIDE: false +0ms
  • nativewind enhanceMiddleware.setup +296ms

Hitting a dead end after hours of trying to get this to work :/

jentzon avatar Jan 09 '25 20:01 jentzon

@jentzon check the path of you global.css, this solved my issue, but I needed to update the path of my global.css

Shooksie avatar Jan 11 '25 14:01 Shooksie

my issue is more related to the fact that react-native-css-interop is not found by eas build, which makes sense when i run the app locally i always have to run npx expo install nativewind in order for the react-native-css-interop to be set in my root node_modules

ioeldev avatar Jan 12 '25 06:01 ioeldev

@jentzon check the path of you global.css, this solved my issue, but I needed to update the path of my global.css

Thanks for your response! I've double and triple checked basically all paths in my configs 😅 Including the global.css file. I've also tried moving this around but without result. One thing to mention perhaps is that I'm using expo-router with the auth wrap (https://docs.expo.dev/router/reference/authentication/). Anyone else that has problems and uses expo-router wrap like their suggestion for auth? Or are there problems without it as well? I'm loading the global.css file within my _layout.tsx file which is the first entry point after expo-router that I control. The file content is below;

import React from 'react';
import { SessionProvider } from '../auth/authCtx';
import { Slot } from 'expo-router';

import '../styles/global.css';


export const App = () => {

  return (
    <SessionProvider>
      <Slot />
    </SessionProvider>
  );
};

export default App;

Anyhow, perhaps others here can compare set ups to what I've mentioned and we can drill down to some problematic area together and find what the problem is 🤗

jentzon avatar Jan 12 '25 15:01 jentzon

@jentzon I am actually using it with router, mine is a little different since I am doing it inside a NX monorepo so I didn't get the default router that comes out of the box. But I have also setup it up using the router template. My recommendation is re-trace your steps, setup a brand new app and re-apply the steps. If that works, then compare what is different.

Shooksie avatar Jan 12 '25 20:01 Shooksie

After trouble shooting, The issue seems to be metro config related and I have been struggling on this from a month now.

image

Git repo: https://github.com/Shanie1331/Test

Created same issue on Nx: https://github.com/nrwl/nx/issues/29624

Here is my metro config

const { withNxMetro } = require('@nx/expo');
const { getDefaultConfig } = require('@expo/metro-config');
const { mergeConfig } = require('metro-config');
const exclusionList = require('metro-config/src/defaults/exclusionList');
const { withNativeWind } = require('nativewind/metro');

const defaultConfig = getDefaultConfig(__dirname);
const { assetExts, sourceExts } = defaultConfig.resolver;

/**
 * Metro configuration
 * https://reactnative.dev/docs/metro
 *
 * @type {import('metro-config').MetroConfig}
 */
const customConfig = {
  cacheVersion: 'Demo',
  transformer: {
    babelTransformerPath: require.resolve('react-native-svg-transformer'),
  },
  resolver: {
    assetExts: assetExts.filter((ext) => ext !== 'svg'),
    sourceExts: [...sourceExts, 'cjs', 'mjs', 'svg'],
    blockList: exclusionList([/^(?!.*node_modules).*\/dist\/.*/]),
    // unstable_enableSymlinks: true,
    // unstable_enablePackageExports: true,
  },
};

const nativeWindConfig = withNativeWind(
  mergeConfig(defaultConfig, customConfig),
  {
    input: './global.css',
  }
);

module.exports = withNxMetro(nativeWindConfig, {
  // Change this to true to see debugging info.
  // Useful if you have issues resolving modules
  debug: false,
  // all the file extensions used for imports other than 'ts', 'tsx', 'js', 'jsx', 'json'
  extensions: [],
  // Specify folders to watch, in addition to Nx defaults (workspace libraries and node_modules)
  watchFolders: [],
});

Shanie1331 avatar Jan 15 '25 05:01 Shanie1331

@Shanie1331, a referenced this link in my first comment up above - https://github.com/nativewind/nativewind/issues/1089#issuecomment-2370432529. This will probably help you resolve the metro config issue.

jentzon avatar Jan 15 '25 13:01 jentzon

I have a working expo application running in my nx workspace. Try adding this to your tailwind.config.js:

const { createGlobPatternsForDependencies } = require('@nx/react/tailwind');
const { join } = require('path');

module.exports = {
  content: [
    join(
      __dirname,
      '{src,pages,components,app}/**/*!(*.stories|*.spec).{ts,tsx,html}',
    ),
    ...createGlobPatternsForDependencies(__dirname),
  ],
...etc

metro.config.js:

const { getDefaultConfig } = require('@expo/metro-config');
const { withNxMetro } = require('@nx/expo');
const { mergeConfig } = require('metro-config');
const { withNativeWind } = require('nativewind/metro');

async function config() {
  const defaultConfig = getDefaultConfig(__dirname);
  const { assetExts, sourceExts } = defaultConfig.resolver;

  /**
   * Metro configuration
   * https://facebook.github.io/metro/docs/configuration
   *
   * @type {import('metro-config').MetroConfig}
   */
  const customConfig = {
    transformer: {
      babelTransformerPath: require.resolve('react-native-svg-transformer'),
    },
    resolver: {
      assetExts: assetExts.filter((ext) => ext !== 'svg'),
      sourceExts: [...sourceExts, 'svg'],
    },
  };

  const nxMetroConfig = await withNxMetro(
    mergeConfig(defaultConfig, customConfig),
    {
      // Change this to true to see debugging info.
      // Useful if you have issues resolving modules
      debug: false,
      // all the file extensions used for imports other than 'ts', 'tsx', 'js', 'jsx', 'json'
      extensions: [],
      // Specify folders to watch, in addition to Nx defaults (workspace libraries and node_modules)
      watchFolders: [],
    },
  );

  const finalConfig = withNativeWind(nxMetroConfig, {
    input: './global.css',
  });

  return finalConfig;
}

module.exports = config();

kirstilynn avatar Jan 15 '25 21:01 kirstilynn

Hey @jentzon, I've never used nx so will be tough for me to assist here but a couple things I would consider:

  • why are you including "./app/**/*.{js,jsx,ts,tsx}", in your content array if your app doesn't have that path?
  • withNxMetro seems to return a Promise so you likely want to await that in your metro.config.js, similar to what @kirstilynn has
  • similar to @Shooksie's suggestion, go back through step by step in a first principle manner to reason about what each addition is actually doing

Regardless, I will note this and surface at our next office hours (held in our Discord) with Mark.

danstepanov avatar Jan 16 '25 23:01 danstepanov

Hi, thanks to everyone here, i managed to make it work on both the web and Android versions with NX as follows:

Note: It also works (with proper config) with expo-router

Finally, the only effective workaround for me was to precede the NativeWind configuration with the NX Metro configuration in metro.config.js

[nx-workspace]/apps/mobile

tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  presets: [require("nativewind/preset")],
  theme: {
    extend: {},
  },
  plugins: [],
}

global.css

@tailwind base;
@tailwind components;
@tailwind utilities;

.babelrc.js

module.exports = function(api) {
  api.cache(true)
  return {
    presets: [
      ["babel-preset-expo", {jsxImportSource: "nativewind"}],
      "nativewind/babel",
    ],
    plugins: [
      "react-native-reanimated/plugin",
    ]
  }
}

metro.config.js

const { getDefaultConfig } = require('expo/metro-config')
const { withNativeWind } = require('nativewind/metro')
const { withNxMetro } = require('@nx/expo')

module.exports = Promise.resolve(getDefaultConfig(__dirname))
  .then(config => withNxMetro(config, {
    debug: false,
    extensions: [],
    watchFolders: []
  }))
  .then((config) => withNativeWind(config, { input: './global.css' }))

index.js / index.ts

import './global.css';
import { registerRootComponent } from 'expo';
import App from './src/app/App';
registerRootComponent(App);

src/app/App.tsx

import {StatusBar} from 'expo-status-bar'
import {StyleSheet, Text, View} from 'react-native'

export default function App() {
  return (
    <View style={styles.container}>
      <Text className="text-red-500 bg-blue-500">Open up App.tsx to start working on your app!</Text>
      <Text className="text-green-500 text-lg font-medium">Welcome to Tailwind</Text>
      <StatusBar style="auto"/>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center'
  }
})

r3gisrc avatar Jan 17 '25 23:01 r3gisrc

@r3gisrc The metro config / promise was the missing key for me. Thanks!

.... all the nx stuff
module.exports = withNxMetro(mergeConfig(defaultConfig, customConfig), {
  // Change this to true to see debugging info.
  // Useful if you have issues resolving modules
  debug: false,
  // all the file extensions used for imports other than 'ts', 'tsx', 'js', 'jsx', 'json'
  extensions: [],
  // Specify folders to watch, in addition to Nx defaults (workspace libraries and node_modules)
  watchFolders: [],
}).then((config) => withNativeWind(config, { input: './global.css' }));

smasala avatar Jan 19 '25 19:01 smasala

Thanks @r3gisrc, I'll add this to the documentation unless you'd be willing to add a page in the "Setup" section for getting started with NX.

danstepanov avatar Jan 22 '25 23:01 danstepanov

@danstepanov https://github.com/nativewind/nativewind/pull/1387

smasala avatar Jan 22 '25 23:01 smasala

I already solved it https://github.com/nativewind/nativewind/discussions/542#discussioncomment-14598923

rawr-code avatar Oct 05 '25 20:10 rawr-code