metro icon indicating copy to clipboard operation
metro copied to clipboard

Static images fail to load in monorepo on iOS

Open jiroscripts opened this issue 1 month ago • 4 comments

Hi Metro team 👋

I’m opening this new issue because I’m still experiencing the same bug as in #1599, but the original reporter is not active anymore and comments are closed.

Summary

In a monorepo (Yarn workspaces), iOS fails to load static images imported via require(...) when the image file is located outside the app directory.

The behavior is identical to #1599:

  • The image silently fails to display on iOS

jiroscripts avatar Nov 14 '25 19:11 jiroscripts

Hi @jiroscripts. In #1599 the issue was a misplaced metro config. If you’re experiencing a different issue, we’ll need more details and reproduction to investigate.

robhogan avatar Nov 15 '25 00:11 robhogan

Hey @robhogan

I tried to place the metro config at the root of my monorepo without any success sadly

I share you some file here to help to debug:

apps/mobile/package.json

{
    "name": "@bleulibellule/mobile",
    "version": "2.0.5",
    "private": true,
    "reactNativePermissionsIOS": [
        "Notifications",
        "AppTrackingTransparency",
        "LocationWhenInUse",
        "LocationAlways"
    ],
    "scripts": {
        "start": "react-native start"
    },
    "dependencies": {
        "@batch.com/react-native-plugin": "^11.1.0",
        "@bleulibellule/algolia-analytics-logger": "workspace:^",
        "@bleulibellule/analytics-logger": "workspace:^",
        "@bleulibellule/api": "workspace:^",
        "@bleulibellule/assets": "workspace:^",
        "@bleulibellule/deeplink-builder": "workspace:^",
        "@bleulibellule/nominatim-api": "workspace:^",
        "@bleulibellule/react-native-alma-widgets": "workspace:^",
        "@bleulibellule/storyblok-renderer": "workspace:^",
        "@bleulibellule/themes": "workspace:^",
        "@bleulibellule/ui": "workspace:^",
        "@didomi/react-native": "^2.18.0",
        "@invertase/react-native-apple-authentication": "^2.4.1",
        "@jiroscripts/react-native-device-brightness": "^0.2.0",
        "@lyracom/react-native-sdk-payment-module": "^3.0.0",
        "@mainbleulibellule/react-native-df-ui-kit": "^1.0.0-beta.6",
        "@maplibre/maplibre-react-native": "^10.4.0",
        "@react-native-async-storage/async-storage": "^2.2.0",
        "@react-native-clipboard/clipboard": "^1.16.3",
        "@react-native-community/blur": "^4.4.1",
        "@react-native-community/datetimepicker": "^8.5.0",
        "@react-native-community/geolocation": "^3.4.0",
        "@react-native-documents/picker": "^11.0.0",
        "@react-native-firebase/analytics": "^23.5.0",
        "@react-native-firebase/app": "^23.5.0",
        "@react-native-firebase/crashlytics": "^23.5.0",
        "@react-native-firebase/remote-config": "^23.5.0",
        "@react-native-google-signin/google-signin": "^16.0.0",
        "@react-native-masked-view/masked-view": "^0.3.2",
        "@react-native-picker/picker": "^2.11.4",
        "@react-native-vector-icons/feather": "^12.3.0",
        "@react-native-vector-icons/material-icons": "^12.3.0",
        "@react-native/virtualized-lists": "^0.81.4",
        "@react-navigation/bottom-tabs": "^7.7.2",
        "@react-navigation/core": "^7.13.0",
        "@react-navigation/elements": "https://github.com/jiroscripts/react-navigation/archive/refs/tags/react-navigation-elements-v2.8.0-gitpkg.tar.gz",
        "@react-navigation/material-top-tabs": "^7.4.1",
        "@react-navigation/native": "^7.1.19",
        "@react-navigation/native-stack": "^7.6.1",
        "@react-navigation/stack": "^7.6.1",
        "@rnx-kit/metro-config": "^2.2.0",
        "@rnx-kit/metro-resolver-symlinks": "^0.2.7",
        "@rnx-kit/tools-node": "^3.0.2",
        "@rnx-kit/tools-react-native": "^2.3.1",
        "@storybook/addon-essentials": "^8.2.2",
        "@storybook/addon-interactions": "^8.2.2",
        "@storybook/addon-links": "^8.2.2",
        "@storybook/addon-ondevice-actions": "^8.2.0-alpha.1",
        "@storybook/addon-ondevice-backgrounds": "^8.2.0-alpha.1",
        "@storybook/addon-ondevice-controls": "^8.2.0-alpha.1",
        "@storybook/addon-ondevice-notes": "^8.2.0-alpha.1",
        "@storybook/addon-react-native-server": "^0.0.5",
        "@storybook/addon-react-native-web": "^0.0.22",
        "@storybook/blocks": "^8.2.2",
        "@storybook/builder-webpack5": "^8.2.2",
        "@storybook/core-common": "^8",
        "@storybook/docs-tools": "^8",
        "@storybook/global": "^5.0.0",
        "@storybook/react": "^8.2.2",
        "@storybook/react-native": "^8.2.0-alpha.1",
        "@storybook/react-native-theming": "^8.2.0-alpha.1",
        "@storybook/react-webpack5": "^8.2.2",
        "@storybook/test": "^8.2.2",
        "@tanstack/query-core": "^5.90.8",
        "@tanstack/react-query": "^5.90.5",
        "@turf/bbox": "^7.2.0",
        "@turf/distance": "^7.2.0",
        "@turf/helpers": "^7.2.0",
        "algoliasearch": "^5.42.0",
        "axios": "^1.13.1",
        "base-64": "^1.0.0",
        "dayjs": "^1.11.18",
        "deepmerge": "^4.3.1",
        "formik": "^2.4.6",
        "geojson": "^0.5.0",
        "i18next": "^25.6.0",
        "instantsearch.js": "^4.81.0",
        "jotai": "^2.15.0",
        "jotai-tanstack-query": "^0.11.0",
        "jsbarcode": "^3.12.1",
        "lodash.debounce": "^4.0.8",
        "luxon": "^3.7.2",
        "moment": "^2.30.1",
        "openapi-fetch": "^0.15.0",
        "react": "19.1.0",
        "react-i18next": "^16.2.2",
        "react-instantsearch-core": "^7.17.0",
        "react-native": "0.81.4",
        "react-native-blob-util": "^0.23.1",
        "react-native-config": "^1.5.9",
        "react-native-credentials-manager": "^0.8.1",
        "react-native-device-info": "^14.1.1",
        "react-native-edge-to-edge": "^1.7.0",
        "react-native-fbsdk-next": "^13.4.1",
        "react-native-gesture-handler": "^2.29.0",
        "react-native-hole-view": "patch:react-native-hole-view@npm%3A3.0.1#~/.yarn/patches/react-native-hole-view-npm-3.0.1-c00ea6c202.patch",
        "react-native-image-picker": "^8.2.1",
        "react-native-image-resizer": "^1.4.5",
        "react-native-keyboard-aware-scroll-view": "^0.9.5",
        "react-native-maps": "^1.26.18",
        "react-native-mmkv": "^4.0.0",
        "react-native-monorepo-tools": "^1.2.1",
        "react-native-network-logger": "^2.0.1",
        "react-native-nitro-modules": "^0.31.3",
        "react-native-pager-view": "^6.9.1",
        "react-native-permissions": "^5.4.3",
        "react-native-phone-input": "^1.3.7",
        "react-native-picker-select": "^9.3.1",
        "react-native-reanimated": "^4.1.3",
        "react-native-reanimated-carousel": "^4.0.3",
        "react-native-render-html": "patch:react-native-render-html@npm%3A6.3.4#~/.yarn/patches/react-native-render-html-npm-6.3.4-35624feabf.patch",
        "react-native-safe-area-context": "^5.5.2",
        "react-native-screenguard": "^1.1.0",
        "react-native-screens": "^4.18.0",
        "react-native-size-matters": "^0.4.2",
        "react-native-svg": "^15.14.0",
        "react-native-tab-view": "^4.2.0",
        "react-native-toast-message": "^2.3.3",
        "react-native-turbo-image": "^1.23.1",
        "react-native-unistyles": "^3.0.17",
        "react-native-url-polyfill": "^3.0.0",
        "react-native-video": "^7.0.0-alpha.7",
        "react-native-vision-camera": "^4.7.2",
        "react-native-volume-manager": "^2.0.8",
        "react-native-webview": "^13.16.0",
        "react-native-worklets": "^0.5.2",
        "react-native-youtube-iframe": "^2.4.1",
        "react-native-zendesk-messaging": "patch:react-native-zendesk-messaging@npm%3A0.3.2#~/.yarn/patches/react-native-zendesk-messaging-npm-0.3.2-7900bb146f.patch",
        "rn-range-slider": "^2.2.2",
        "search-insights": "^2.17.3",
        "semver": "^7.7.3",
        "string-strip-html": "^13.5.0",
        "styled-components": "^6.1.19",
        "uuid-random": "^1.3.2",
        "warn-once": "^0.1.1",
        "yup": "^1.7.1"
    },
    "devDependencies": {
        "@babel/core": "^7.25.2",
        "@babel/preset-env": "^7.25.3",
        "@babel/runtime": "^7.25.0",
        "@nx/eslint-plugin": "^22.0.2",
        "@nx/react-native": "^22.0.2",
        "@react-native-community/cli": "20.0.0",
        "@react-native-community/cli-platform-android": "20.0.0",
        "@react-native-community/cli-platform-ios": "20.0.0",
        "@react-native/babel-preset": "0.81.4",
        "@react-native/eslint-config": "0.81.4",
        "@react-native/metro-config": "0.81.4",
        "@react-native/typescript-config": "0.81.4",
        "@testing-library/react-native": "^13.3.3",
        "@types/babel__core": "^7",
        "@types/babel__preset-env": "^7",
        "@types/base-64": "^1",
        "@types/geojson": "^0",
        "@types/jest": "^29.5.13",
        "@types/lodash.debounce": "^4",
        "@types/luxon": "^3",
        "@types/node": "^24.9.2",
        "@types/react": "^19.1.0",
        "@types/react-test-renderer": "^19.1.0",
        "@types/semver": "^7",
        "babel-plugin-module-resolver": "^5.0.2",
        "babel-plugin-transform-remove-console": "^6.9.4",
        "eslint": "^8.19.0",
        "jest": "^29.6.3",
        "jsonc-eslint-parser": "^2.4.1",
        "prettier": "2.8.8",
        "react-native-svg-transformer": "^1.5.1"
    }
}

package.json

{
    "name": "@bleulibellule/source",
    "version": "2.0.5",
    "private": true,
    "scripts": {
        "test": "jest --coverage",
        "lint": "eslint ./src --ext .js,.jsx,.ts,.tsx",
        "jscpd": "jscpd src -o reports/jscpd -r html",
        "cpx": "genese cpx src",
        "quality": "npm run lint && npm run jscpd",
        "sonar": "sonar-scanner && open http://localhost:9000/dashboard?id=BleuLibellule",
        "upgrade-version": "./scripts/version-upgrader.sh"
    },
    "engines": {
        "node": ">=20"
    },
    "packageManager": "[email protected]",
    "lint-staged": {
        "*.{js,jsx,ts,tsx,json}": [
            "prettier --write",
            "eslint --fix"
        ]
    },
    "commitlint": {
        "extends": [
            "@commitlint/config-conventional"
        ]
    },
    "workspaces": {
        "packages": [
            "apps/*",
            "libs/*"
        ]
    },
    "devDependencies": {
        "@nx/eslint": "^22.0.2",
        "@nx/eslint-plugin": "^22.0.2",
        "@nx/jest": "^22.0.2",
        "@nx/js": "22.0.2",
        "@nx/next": "^22.0.2",
        "@nx/react-native": "22.0.2",
        "@nx/web": "22.0.2",
        "@nx/workspace": "22.0.2",
        "@rnx-kit/metro-config": "^2.2.0",
        "@swc-node/register": "~1.9.1",
        "@swc/core": "~1.5.7",
        "@swc/helpers": "~0.5.11",
        "@types/node": "20.19.9",
        "@types/react": "^19.0.10",
        "@types/react-dom": "^19.0.6",
        "@typescript-eslint/eslint-plugin": "^8.46.2",
        "@typescript-eslint/parser": "^8.46.2",
        "eslint": "^9.38.0",
        "jiti": "2.4.2",
        "jsdom": "~22.1.0",
        "jsonc-eslint-parser": "^2.4.1",
        "lint-staged": "^16.2.6",
        "nx": "22.0.2",
        "swc-node": "^1.0.0",
        "ts-node": "^10.9.2",
        "tslib": "^2.3.0",
        "typescript": "~5.9.2"
    }
}

metro.config.js

const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const { makeMetroConfig } = require('@rnx-kit/metro-config');
const path = require('path');

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

const workspaceRoot = __dirname;
const projectRoot = path.resolve(workspaceRoot, 'apps/mobile');

/**
 * Metro configuration
 * https://reactnative.dev/docs/metro
 *
 * @type {import('@react-native/metro-config').MetroConfig}
 */
const customConfig = {
    cacheVersion: '@bleulibellule/mobile',
    projectRoot,
    watchFolders: [workspaceRoot],
    transformer: {
        babelTransformerPath: require.resolve(
            'react-native-svg-transformer/react-native'
        ),
    },
    resolver: {
        assetExts: [...assetExts.filter((ext) => ext !== 'svg'), 'mp4'],
        sourceExts: [...sourceExts, 'cjs', 'mjs', 'svg', 'ts', 'tsx'],
        nodeModulesPaths: [
            path.resolve(workspaceRoot, 'node_modules'),
            path.resolve(projectRoot, 'node_modules'),
        ],
    },
};

module.exports = mergeConfig(defaultConfig, customConfig);

App.tsx

import { Image } from 'react-native';

import backIconSource from '@react-navigation/elements/lib/module/assets/back-icon.png';

export const App = () => {
    return (
        <Image
            source={backIconSource}
            style={{ width: 100, height: 100, backgroundColor: 'red' }}
        />
    );
};

export default App;

EDIT

The Console app reports these messages:

par défaut	15:13:13.276158+0100	BleuLibellule	[C24 694EAF98-E997-4181-BF1C-33E5419385C0 Hostname#e16a780d:8081 tcp, url: http://localhost:8081/assets/../../libs/assets/src/lib/images/No_image_available.png, definite, attribution: developer] cancel
par défaut	15:13:13.276376+0100	BleuLibellule	[C24 694EAF98-E997-4181-BF1C-33E5419385C0 Hostname#e16a780d:8081 tcp, url: http://localhost:8081/assets/../../libs/assets/src/lib/images/No_image_available.png, definite, attribution: developer] cancelled
	[C24.1.1 6455B039-BBFC-44B5-A452-A7D2C31A9E04 ::1.49675<->::1.8081]
	Connected Path: satisfied (Path is satisfied), viable, interface: lo0
	Privacy Stance: Not Eligible
	Duration: 30.626s, DNS @0.001s took 0.001s, TCP @0.003s took 0.000s
	bytes in/out: 603/321, packets in/out: 2/1, rtt: 0.001s, retransmitted bytes: 0, out-of-order bytes: 0
	ecn packets sent/acked/marked/lost: 0/0/0/0

jiroscripts avatar Nov 17 '25 10:11 jiroscripts

In package.json

"resolutions": { "metro": "0.82.5", "metro-config": "0.82.5", "metro-core": "0.82.5" }

I've rolled back the version for now, and that helped me.

dsshard avatar Nov 22 '25 00:11 dsshard

@dsshard You rolled back to which version ?

I found a metro config that works but you need to make some ajustements to make it works. The key is to define projectRoot at the root of the monorepo cause iOS do not suport uri with double dots (..) inside

It is actually an issue only in iOS in debug mode cause of the metro server assets paths

jiroscripts avatar Nov 22 '25 09:11 jiroscripts