인터페이스는 현지화되어 있으며, Skill 소개와 설명 문서는 선택한 언어로 AI가 자동 번역합니다. 코드·명령어·경로·전문 용어는 원문을 유지하며 원작자 문서가 우선합니다.
Skills 둘러보기
생산성 및 협업 · SKILL.md

react-native-monorepo-config

react-native-monorepo-config

Configures React Native projects in monorepo environments using npm/yarn workspaces, Metro bundler, shared packages, and cross-platform module resolution.

Independent GitHub snapshot · Automated classification · Original documentation governs

Skill 문서

GitHub

name: react-native-monorepo-config description: Configures React Native projects in monorepo environments using npm/yarn workspaces, Metro bundler, shared packages, and cross-platform module resolution. license: MIT compatibility: opencode archetypes:

  • tactical
  • generation anti_triggers:
  • brainstorming
  • vague ideation
  • long-form architecture response_profile: verbosity: low directive_strength: high abstraction_level: operational metadata: version: "1.0.0" domain: coding triggers: react native monorepo, metro config, yarn workspaces, shared packages, react native config, monorepo setup, cross-platform role: implementation scope: implementation output-format: code content-types: [code, guidance, do-dont, examples, config] related-skills: react-native-state-management, react-native-rendering author: https://github.com/vercel-labs source: https://github.com/vercel-labs/agent-skills

React Native Monorepo Configuration

A senior build engineer who configures React Native projects within monorepo workspaces — wiring Metro bundler to watch all packages, resolving modules across workspace boundaries, linking native modules, and sharing TypeScript/ESLint/Prettier configs across application and library packages.

TL;DR Checklist

  • Initialize with npm or yarn workspaces pointing to apps/* and packages/*
  • Configure Metro's watchFolders to include all workspace package directories
  • Set nodeModulesPaths / moduleDirs for cross-workspace module resolution
  • Create react-native.config.js for native module linking across packages
  • Add .watchmanconfig to the monorepo root for proper file watching
  • Use BlockList in metro.config.js to exclude large generated directories (node_modules, build)
  • Share TypeScript config via extends pattern across all packages
  • Configure Jest with projects for per-package test configuration

When to Use

Use this skill when:

  • Setting up a new React Native app inside an existing monorepo with shared packages
  • Migrating a standalone React Native app into a monorepo with npm/yarn workspaces
  • Adding a shared UI component library that must work across iOS and Android
  • Configuring Metro to resolve modules from sibling packages in a workspaces setup
  • Debugging "module not found" errors in a React Native monorepo
  • Standardizing TypeScript, ESLint, and Prettier across multiple React Native packages

When NOT to Use

Avoid this skill for:

  • Standalone React Native apps outside a monorepo — the default Metro config suffices
  • Expo-managed workflows (bare workflow may still need this) — Expo's built-in monorepo support handles most cases
  • Pure web monorepos without React Native — different bundling tools (webpack, Vite) are involved
  • Prototyping where monorepo overhead is not yet justified

Core Workflow

  1. Initialize Workspaces — Create the monorepo root with workspace configuration:

    • Use yarn workspaces (more mature monorepo support) or npm workspaces
    • Structure packages as apps/* (applications) and packages/* (shared libraries)
    • Set "private": true at root with "workspaces" config Checkpoint: Run yarn install — verify packages link correctly in node_modules.
  2. Configure Metro — The critical step for React Native monorepos:

    • Add all workspace package directories to watchFolders
    • Configure nodeModulesPaths to resolve modules across workspaces
    • Add BlockList patterns for node_modules outside workspace packages
    • Set projectRoot explicitly to avoid ambiguity Checkpoint: Run npx react-native start — Metro must bundle without "module not found" errors.
  3. Set Up Native Module Linking — Bridge React Native packages across workspaces:

    • Create react-native.config.js in app packages to declare dependencies
    • Configure dependency.platforms for iOS (podspec path) and Android (gradle link)
    • Run pod install in the iOS directory to link native pods Checkpoint: Verify native modules (camera, maps, gestures) build on both platforms.
  4. Share Configuration — Unify tooling across packages:

    • Create packages/config/ with shared tsconfig.json, .eslintrc.js, .prettierrc.js
    • App and library packages extend these with extends: '../../packages/config/tsconfig.json'
    • Share Jest config with jest.config.base.js and per-package overrides Checkpoint: Run tsc --noEmit and eslint . from root — both must pass with shared configs.
  5. Configure File Watching — Ensure monorepo file changes trigger Metro rebuilds:

    • Add .watchmanconfig with {} at the monorepo root
    • Ensure watchman is installed and version >= 2022.02.14.00
    • Add watchFolders to Metro config for all symlinked package directories Checkpoint: Make a change in a shared package and verify Metro triggers a rebuild.

Implementation Patterns

Pattern 1: Root Workspace Config

// ❌ BAD: Missing private: true, no workspaces config, flat node_modules
{
  "name": "my-app",
  "version": "1.0.0"
}

// ✅ GOOD: Proper workspaces config with app and package directories
{
  "name": "my-monorepo",
  "version": "1.0.0",
  "private": true,
  "workspaces": [
    "apps/*",
    "packages/*"
  ],
  "scripts": {
    "android": "yarn workspace @my/app android",
    "ios": "yarn workspace @my/app ios",
    "start": "yarn workspace @my/app start",
    "lint": "eslint . --ext .ts,.tsx",
    "typecheck": "tsc --noEmit --pretty",
    "test": "jest"
  },
  "devDependencies": {
    "typescript": "^5.4.0",
    "eslint": "^8.57.0",
    "prettier": "^3.2.0",
    "jest": "^29.7.0"
  }
}

Pattern 2: Metro Configuration for Monorepo

// metro.config.js (at apps/my-app/)
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const path = require('path');

const workspaceRoot = path.resolve(__dirname, '../..');
const appRoot = __dirname;

/**
 * Metro configuration for React Native monorepo.
 *
 * watchFolders: tells Metro to monitor all workspace package directories.
 * nodeModulesPaths: resolves require('module') across workspace boundaries.
 * blockList: prevents Metro from crawling large extraneous directories.
 */
const config = {
  projectRoot: appRoot,
  watchFolders: [
    workspaceRoot,
    // Explicitly add shared package directories so Metro watches them
    path.resolve(workspaceRoot, 'packages/shared-ui'),
    path.resolve(workspaceRoot, 'packages/config'),
    path.resolve(workspaceRoot, 'packages/helpers'),
  ],
  resolver: {
    nodeModulesPaths: [
      // Resolve modules from the app's node_modules first, then hoisted root
      path.resolve(appRoot, 'node_modules'),
      path.resolve(workspaceRoot, 'node_modules'),
    ],
    // Prevent Metro from crawling every node_modules in every workspace
    blockList: [
      /.*\/node_modules\/.*\/node_modules\/.*/,
      /.*\/__pycache__\/.*/,
      /.*\/\.git\/.*/,
      /.*\/build\/.*/,
      /.*\/\.expo\/.*/,
    ],
    // Ensure sourceExts covers shared package file types
    sourceExts: ['js', 'jsx', 'ts', 'tsx', 'json', 'svg'],
  },
  transformer: {
    // Required for monorepo symlinks
    minifierConfig: {
      keep_classnames: true,
      keep_fnames: true,
    },
  },
};

module.exports = mergeConfig(getDefaultConfig(appRoot), config);

Pattern 3: Native Module Linking

// ❌ BAD: No react-native.config.js — native modules in workspace packages won't link
// (metro cannot resolve native modules from symlinked packages without this config)

// ✅ GOOD: react-native.config.js at the app level declares workspace dependency linkage
// apps/my-app/react-native.config.js
module.exports = {
  project: {
    ios: {
      sourceDir: './ios',
    },
    android: {
      sourceDir: './android',
    },
  },
  dependencies: {
    // Map workspace packages to their native module locations
    '@my/shared-ui': {
      root: path.resolve(__dirname, '../../packages/shared-ui'),
      platforms: {
        ios: {
          podspecPath: path.resolve(
            __dirname,
            '../../packages/shared-ui/ios/SharedUI.podspec'
          ),
        },
        android: {
          sourceDir: path.resolve(
            __dirname,
            '../../packages/shared-ui/android'
          ),
        },
      },
    },
  },
};

// ✅ GOOD: Workspace package declares its native module in package.json
// packages/shared-ui/package.json
{
  "name": "@my/shared-ui",
  "version": "1.0.0",
  "main": "src/index.ts",
  "react-native": "src/index.ts",
  "peerDependencies": {
    "react": "^18.2.0",
    "react-native": "^0.73.0"
  },
  "publishConfig": {
    "access": "public"
  }
}

Pattern 4: Shared TypeScript Config

// ❌ BAD: Each package duplicates its own TypeScript config — drifts over time
// packages/shared-ui/tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "moduleResolution": "node",
    "jsx": "react-native",
    "target": "esnext"
  }
}
// apps/my-app/tsconfig.json — same thing copy-pasted
{
  "compilerOptions": {
    "strict": true,
    "moduleResolution": "node",
    "jsx": "react-native",
    "target": "esnext"
  }
}

// ✅ GOOD: Single shared config with per-package overrides
// packages/config/tsconfig.base.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "react-native",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "baseUrl": ".",
    "paths": {
      "@my/*": ["packages/*/src"]
    }
  },
  "exclude": ["node_modules", "build", "dist"]
}

// packages/shared-ui/tsconfig.json — extends the base
{
  "extends": "../../packages/config/tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src"]
}

// apps/my-app/tsconfig.json — extends the same base
{
  "extends": "../../packages/config/tsconfig.base.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src"]
}

Pattern 5: Shared ESLint + Prettier Config

// packages/config/.eslintrc.js
module.exports = {
  root: true,
  extends: [
    '@react-native',
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'prettier',
  ],
  plugins: ['@typescript-eslint', 'react', 'react-hooks'],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: { jsx: true },
    ecmaVersion: 'latest',
    sourceType: 'module',
  },
  rules: {
    'react-hooks/rules-of-hooks': 'error',
    'react-hooks/exhaustive-deps': 'warn',
    '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
    'react-native/no-inline-styles': 'error',
  },
  ignorePatterns: ['node_modules', 'build', 'dist', '.expo'],
};

// .eslintrc.js at the monorepo root — just points to shared config
module.exports = {
  root: true,
  extends: ['./packages/config/.eslintrc.js'],
};

// packages/config/.prettierrc.js
module.exports = {
  semi: true,
  trailingComma: 'all',
  singleQuote: true,
  printWidth: 100,
  tabWidth: 2,
  bracketSpacing: true,
  arrowParens: 'always',
};

// .prettierrc.js at root — delegates to shared config
module.exports = {
  ...require('./packages/config/.prettierrc.js'),
};

Constraints

MUST DO

  • Configure Metro's watchFolders to include all workspace package directories
  • Set nodeModulesPaths to resolve modules across workspace boundaries
  • Create react-native.config.js in each app package for native module linking
  • Add .watchmanconfig at the monorepo root for reliable file change detection
  • Share TypeScript, ESLint, and Prettier configs via a central packages/config package
  • Use the extends pattern in tsconfig.json to share compilation options
  • Configure Jest with projects or a root config that covers all workspace packages

MUST NOT DO

  • Use hoisting settings that break native module resolution — prefer nohoist for react-native packages
  • Forget to add all workspace package paths to Metro's watchFolders
  • Ignore "module not found" errors in CI — they hide broken workspace resolution
  • Duplicate TypeScript/ESLint/Prettier configs across packages — they will drift
  • Use npm workspaces without verifying Metro compatibility (yarn workspaces has more mature RN support)
  • Ignore the blockList in Metro config — crawling every nested node_modules causes OOM crashes

Related Skills

SkillPurpose
react-native-state-managementState management library shared across monorepo packages
react-native-renderingRendering optimizations shared between app and component library packages

Live References

Authoritative documentation links for React Native monorepo configuration.

저장한 Skills