📕

モノレポ構成で実現する『React Native Expo × Vite React』環境構築ガイド

に公開

はじめに

現在モバイルエンジニアとして活動している、のぞみんです。

本記事では、React Native(Expo)のモバイルアプリと、Vite + React のWeb管理画面を、モノレポ構成でまとめて開発できるようにするための環境構築手順を紹介します。

万人向けというより、「まさに今それをやりたい!」という方向けの内容になります。必要なところだけ拾って使ってもらえたら嬉しいです 🙇‍♂️

概要

以前「こういうアプリあったら便利やな〜」と思ったのがきっかけで、React Native(Expo)で個人開発を進めています。

React Native を選んだ理由は、Webに近い感覚で開発できることと、マルチプラットフォーム開発に挑戦したかったからです。


開発を進める中で「管理画面もReactでSPAにしよう」となり、モバイルは React Native(Expo)/Webは Vite + React で作る方針にしました。

ただ、ここで悩んだのが リポジトリの構成です。アプリと管理画面を別々に管理すると、共通コードの共有や依存管理が面倒になりそうでした。

そこで本記事では、モノレポ構成でこの2つをまとめ、開発しやすい形に整える手順をまとめます。モノレポでReact系を扱う事例も増えているので、その考え方に沿って進めます。

本記事で分かること

  • モノレポ構成とは何か?
  • モノレポ構成にする利点
  • 実際の環境構築手順

第1章:モノレポ構成とは?

リポジトリ管理の2つのアプローチ

ソフトウェア開発において、ソースコードを管理する方法は大きく2つに分けられるかと思います。


1つ目は『 ポリレポ(Polyrepo) 』と呼ばれる方法です。
これは、プロジェクトごとに独立したリポジトリを作成するアプローチです。

一般的にはこの方式でソースコードを管理することが多いのではないでしょうか?🧐
(自分は今までこの方式でしか管理したことがありませんでした)


2つ目は、本記事のタイトルにも出ている『 モノレポ(Monorepo) 』です。
モノレポは「Monolithic Repository」の略で、複数のプロジェクトを1つのリポジトリで管理する方式のことみたいです!

モノレポの構造を理解する

さて...そんなモノレポですが。
基本的な構造としては1つのリポジトリの中に複数の「ワークスペース」を持つ形になります。

各ワークスペースは独立したプロジェクト(パッケージ)として機能しますが、同じリポジトリ内で管理されることになるため、コードの共有・一括管理が容易になります。


具体的な例として、toC向けのモバイルアプリを開発するケースで考えてみたいと思います!
このアプリには、「ユーザーが使用するモバイルアプリ」「運営者が使用する管理画面」の2つが必要になります。モノレポ構成では以下のようなディレクトリ構造になります。

toC_app/                          ← 1つのGitリポジトリ
├── apps/
│   ├── mobile/                    ← React Native Expo(モバイルアプリ)
│   │   ├── app/
│   │   ├── components/
│   │   └── package.json
│   └── admin/                     ← Vite + React(管理画面Web)
│       ├── src/
│       └── package.json
├── packages/
│   └── shared/                    ← 共有コード(型定義、ユーティリティなど)
│       ├── src/
│       └── package.json
├── package.json                   ← ルートの設定ファイル
└── README.md

この構造のポイントとして...
apps/ディレクトリに各アプリケーションを配置し、packages/ディレクトリに共有コードを配置する』という整理方法です。
ルートに配置しているpackage.jsonがこれら全てを束ねる役割を果たしています!

npm workspaces の役割

さて...ここまで説明したモノレポ。
これを実現するためのツールとして、本記事ではnpmに標準搭載されている『npm workspaces』を使用して説明したいと思います。

npm workspaces は、npmバージョン7以降で利用可能な機能となっておあり、追加のツールをインストールすることなくモノレポを構築することができます!

npm workspacesを有効にするには、先ほど説明したディレクトリ構造のルートに配置されているpackage.jsonに、workspacesフィールドを追加する必要があります。

{
  "name": "toC_app",
  "private": true,
  "workspaces": [
    "apps/*",
    "packages/*"
  ]
}

この設定によって、apps/packages/配下の各ディレクトリが独立したワークスペースとして認識されるようになります。
注意点として、"private": trueの指定は必須となっていまして、これによってルートパッケージが誤ってnpmに公開されることを防ぐようになっています。

第2章:モノレポ構成にする利点

さて、ここまでモノレポについて説明してきましたが、こう思われた方も多いのではないのでしょうか?

「モノレポ構成にして何のメリットがあるの???」

そこで、実際の開発現場で特に重要になるであろう5つの利点について、具体例を交えながら説明したいきたいと思います!

1. コードの共有が容易になる

モノレポの最大の利点として、「複数のプロジェクト間でコードを簡単に共有できる」というところにあります。

例えば、モバイルアプリと管理画面の両方で『ユーザー』というデータを扱う場合を考えてみましょう。ポリレポ構成では、それぞれのリポジトリで User の型定義を書く必要があり、同じ内容を2箇所で管理しなければなりません。

// mobile リポジトリの types/user.ts
interface User {
  id: number;
  name: string;
  email: string;
}

// admin リポジトリの types/user.ts(同じ内容を重複して記述)
interface User {
  id: number;
  name: string;
  email: string;
}

この状態でUserに新しいフィールド(例えばcreatedAtなど)を追加する場合、2つのリポジトリでそれぞれ修正が必要になるんですね...
そうなると、片方の修正を忘れる可能性が高くなってしまい、結果としてデータの不整合やバグの原因となってしまいます。


その点モノレポ構成では、共有パッケージに型定義を1箇所だけ記述しておけば、両方のアプリから参照することができるようになります✨👏

// packages/shared/src/types/user.ts
export interface User {
  id: number;
  name: string;
  email: string;
  createdAt: string;  // 追加したフィールド
}


// apps/mobile/...
import { User } from '@toC_app/shared';


// apps/admin/...
import { User } from '@toC_app/shared';

型定義だけでなく、『API通信ロジック』『バリデーション関数』『定数』『ユーティリティ関数』なども同様に共有化することができます。

2. 依存関係の一元管理

モノレポでは、全てのプロジェクトの依存関係を1箇所で管理することができます。
こうすることで、ライブラリのバージョン統一が容易になります。


ポリレポ構成だと...

  • モバイルアプリで React 18.2.0 を使用
  • 管理画面で React 18.1.0 を使用

という状況が起きる可能性があり、バージョン違いによる微妙な挙動の差異によってエラーを引き起こすこともあります。

モノレポでは、ルートで npm install を実行することで、全てのワークスペースの依存関係をまとめてインストールすることができます。
そのため、共通のライブラリは自動的に同じバージョンに揃えられ、 node_modules も効率的に共有されるため、ディスク容量の節約にも繋がります。

3. AI Rule, Linterなどを一貫して管理・適用

『コーディング規約』『リンター設定』『フォーマッター設定』などを全プロジェクトで統一することができます。Claude code RulesやESLint、Prettier の設定ファイルをルートに1つ置くだけで、すべてのプロジェクトで同じルールを適用することができます。
(もちろん、プロジェクトごとにルールを分割することも可能です!)

これにより、モバイルアプリを担当する開発者が管理画面のコードを見たときも、見慣れたコーディングスタイルで書かれているため、すぐに理解することができます。チーム内でのコードレビューもスムーズに行えると思います!

4. アトミックなコミットが可能

『アトミックなコミット』とは、関連する変更を1つのコミットにまとめることです。

例えば、API のレスポンス形式を変更する場合を考えてみましょう。
この変更は、『バックエンド』『モバイルアプリ』『管理画面』の全てに影響します。


ポリレポ構成では、3つのリポジトリでそれぞれ変更をコミットし、プルリクエストを作成し、マージする必要があります。順序を間違えると、一時的にアプリが動作しなくなる期間が生じる可能性があります。

その点モノレポでは、すべての変更を1つのコミットにまとめられます。

commit: "API レスポンス形式の変更に対応"

- packages/shared/src/types/api.ts を更新
- apps/mobile/src/api/users.ts を更新  
- apps/admin/src/api/users.ts を更新

こうすることで変更の追跡が容易になり、問題が発生した場合も1つのコミットを revert するだけで元に戻すことが可能です。
(とはいえ、コミットを分割した方が最適な場合もあるため、これについてはチームでの開発ルールなどで適宜対応する必要がありそうです。)

5. リファクタリングの効率化

大規模なリファクタリングを行う際、モノレポは特に威力を発揮します。


例えば、共有関数の引数を変更する場合。
モノレポではその関数を使用しているすべての箇所を一度に検索し、一度に修正できます。IDE の「すべての参照を検索」機能を使えば、リポジトリの境界を気にすることなく、影響範囲を即座に把握できます。

ポリレポでは、それぞれのリポジトリで検索・修正を行い、さらに変更を同期するタイミングを調整する必要があります。この調整コストは、プロジェクトが増えるほど大きくなります。

第3章:環境構築手順

ここからは、実際に React Native Expo のモバイルアプリと Vite + React の管理画面を、モノレポ構成で管理する環境を構築する手順を解説します。

1. 前提条件

環境構築を始める前に、以下のツールがインストールされていることを確認してください。

  • Node.js(ver.18 以上)
  • npm
  • Git

また、本記事では既存のReact Native Expoプロジェクトがあることを前提としています。新規プロジェクトから始める場合は、後述の手順を適宜調整していただくよう、お願いします🙇‍♂️

2. 既存プロジェクトの確認

まず、現在のプロジェクト構造を確認します。典型的な React Native Expo プロジェクトは、以下のような構造になっているかと思います。

toC_app/
├── .git/
├── app/
├── assets/
├── components/
├── constants/
├── hooks/
├── scripts/
├── .gitignore
├── app.json
├── package.json
├── tsconfig.json
└── README.md

この状態では、リポジトリのルートディレクトリに直接モバイルアプリのファイルが配置されています。モノレポに移行するには、これらを apps/mobile/ ディレクトリに移動し、ルートにワークスペース管理用の設定を追加します。

3. バックアップの作成(作業中の場合のみ)

本番環境に影響を与えないよう、作業前に必ずバックアップを作成するようにしてください。
Gitを使用している場合は、現在の状態をブランチとして保存しておくことをオススメします。

git add .
git commit -m "モノレポ移行前の状態を保存"
git branch backup-before-monorepo

現在の状態が backup-before-monorepoというブランチに保存されます。何か問題が発生した場合は、 git checkout backup-before-monorepo で元の状態に戻しましょう。
(個人的には、可能なら作業中でない状態にモノレポ移行の作業を行うことをオススメします...!)

4. ディレクトリ構造の再編成

モバイルアプリのファイルを apps/mobile/ ディレクトリに移動します。
まず、必要なディレクトリを作成します。

# ルートディレクトリで実行
mkdir -p apps/mobile
mkdir -p packages

mkdir -p コマンドは、指定したディレクトリと、その親ディレクトリが存在しない場合はまとめて作成してくれます。


次に、モバイルアプリのファイルを移動します。
.git ディレクトリと node_modules ディレクトリは移動しないでください。.git はリポジトリのルートに残す必要があり、 node_modules は後で再インストールします。

mv app apps/mobile/
mv assets apps/mobile/
mv components apps/mobile/
mv constants apps/mobile/
mv hooks apps/mobile/
mv scripts apps/mobile/
mv app.json apps/mobile/
mv package.json apps/mobile/
mv package-lock.json apps/mobile/
mv tsconfig.json apps/mobile/
mv eslint.config.js apps/mobile/
mv expo-env.d.ts apps/mobile/
mv .expo apps/mobile/

node_modules ディレクトリが存在する場合は削除しておきます。依存関係については後ほどルートから再インストールするためです。

rm -r node_modules

5. ルート用package.json作成

リポジトリのルートディレクトリに、ワークスペースを管理するための package.json を新規作成します。

{
  "name": "toC_app",
  "version": "1.0.0",
  "private": true,
  "workspaces": [
    "apps/*",
    "packages/*"
  ],
  "scripts": {
    "mobile:start": "npm run start --workspace=apps/mobile",
    "mobile:android": "npm run android --workspace=apps/mobile",
    "mobile:ios": "npm run ios --workspace=apps/mobile",
    "mobile:web": "npm run web --workspace=apps/mobile",
    "admin:dev": "npm run dev --workspace=apps/admin",
    "admin:build": "npm run build --workspace=apps/admin",
    "admin:preview": "npm run preview --workspace=apps/admin"
  },
  "engines": {
    "node": ">=18.0.0"
  }
}
設定ファイル各部分の説明
  • "name" :モノレポ全体を表す名前を指定。npm に公開しないため、任意の名前で問題無し。
  • "private" :true: このパッケージを npm に公開しないことを示す。ワークスペース機能を使用する場合、この設定は必須。
  • "workspaces" :この配列にはワークスペースとして認識するディレクトリのパターンを指定する。"apps/" は apps ディレクトリ内のすべてのサブディレクトリを、"packages/" は packages ディレクトリ内のすべてのサブディレクトリをワークスペースとして扱うことを意味する。
  • "scripts":このセクションでは、ルートから各ワークスペースのコマンドを実行するためのショートカットを定義している。--workspace=apps/mobile オプションにより、指定したワークスペースのスクリプトを実行することが可能となる。
  • "engines" :このプロジェクトが必要とする Node.js のバージョンを明示している。これにより、古いバージョンの Node.js で実行しようとした際に警告が表示される。

6. モバイルアプリのpackage.json修正

apps/mobile/package.json を開き、 "name" フィールドを修正します。ワークスペース内の各パッケージは一意の名前を持つ必要があるため、ここではシンプルに "mobile" としておきます。

{
  "name": "mobile",
  "version": "1.0.0",
  ...
}

7. ルートの.gitignore作成

ルートディレクトリに .gitignore ファイルを新規作成します。モノレポ全体に適用される共通の除外ルールを記述します。
(ファイルの内容は適宜変更してください。)

# 依存関係
# npm install で生成されるディレクトリ。サイズが大きくGit管理に適さない
node_modules/

# 環境変数ファイル
# APIキーなどの秘密情報を含むため、リポジトリに含めない
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# ビルド成果物
# ビルド時に生成されるファイル。ソースコードから再生成可能
dist/
build/
.next/

# OS が自動生成するファイル
.DS_Store
Thumbs.db

# IDE・エディタの設定ファイル
# 個人の開発環境に依存するため、共有しない
.idea/
*.swp
*.swo
.vscode/*
!.vscode/extensions.json

# ログファイル
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Expo 固有(apps/mobile 用)
.expo/
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/

# Vite 固有(apps/admin 用)
*.local

# TypeScript
*.tsbuildinfo

# テスト
coverage/

8. packagesディレクトリの設定

packages ディレクトリは将来の共有コード用に準備しておきます。Git は空のディレクトリを管理できないため、 .gitkeep ファイルを配置しておきましょう。

touch packages/.gitkeep

9. 依存関係のインストール

ルートディレクトリで npm install を実行し、すべてのワークスペースの依存関係をインストールします。

npm install

インストールが完了したら、モバイルアプリが正常に起動するか確認しましょう。
Expoのローカルサーバーが起動し、QRコードが表示されればOKです。

npm run mobile:start

10. 管理画面プロジェクトの作成

モノレポの構造が整ったので、管理画面用の React プロジェクトを追加します。Vite を使用してプロジェクトを作成します。

cd apps
npm create vite@latest admin -- --template react-ts

コマンドを実行すると、apps ディレクトリ内に admin というフォルダが作成され、React + TypeScript のテンプレートが展開されます。 --template react-ts オプションにより、TypeScript が設定済みのテンプレートが使用されます。

11. 管理画面のpackage.json確認

apps/admin/package.json を開き、"name" フィールドが "admin" になっていることを確認します。Vite のテンプレートでは通常、ディレクトリ名がそのまま name として設定されます。

{
  "name": "admin",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  },
  "devDependencies": {
    ...
  }
}

12. 依存関係の再インストール

管理画面プロジェクトを追加したので、ルートで再度 npm install を実行します。

# ルートディレクトリへ移動してから実行
npm install

これにより、adminワークスペースの依存関係もインストールされます。

13. 管理画面に必要なパッケージの追加

管理画面で使用する追加のパッケージをインストールします。--workspace オプションを使用することで、特定のワークスペースにのみパッケージを追加できます。

npm install @mui/material @emotion/react @emotion/styled @mui/icons-material --workspace=apps/admin
npm install react-router-dom --workspace=apps/admin
npm install @tanstack/react-query --workspace=apps/admin
npm install axios --workspace=apps/admin
各パッケージの役割
  • @mui/material :Google の Material Design を実装した React コンポーネントライブラリ。ボタン、テーブル、フォームなど、管理画面に必要な UI 部品が豊富に用意されている。
  • @emotion/react, @emotion/styled :MUI が内部で使用するスタイリングエンジン。MUI を使用する場合は必須の依存関係となる。
  • @mui/icons-material :Material Design アイコンのコレクション。ナビゲーションやアクションボタンにアイコンを追加する際に使用する。
  • react-router-dom:React アプリケーションでページ遷移(ルーティング)を実現するためのライブラリ。
  • @tanstack/react-query :サーバーからのデータ取得とキャッシュを管理するライブラリ。API からデータを取得し、ローディング状態やエラー状態を簡潔に扱えるようになる。
  • axios:HTTP リクエストを送信するためのライブラリ。ブラウザ標準の fetch API より使いやすいインターフェースが提供されている。

16. 管理画面の動作確認

管理画面の開発サーバーを起動して、正常に動作するかを確認してみましょう。

npm run admin:dev

ブラウザで http://localhost:5173 にアクセスし、Vite + React のデフォルトページが表示されればOKです!

最終的なディレクトリ構造の確認

全ての作業が完了すると、以下のようなディレクトリ構造になっているかと思います。

toC_app/
├── .git/
├── apps/
│   ├── mobile/
│   │   ├── app/
│   │   ├── assets/
│   │   ├── components/
│   │   ├── constants/
│   │   ├── hooks/
│   │   ├── scripts/
│   │   ├── .expo/
│   │   ├── app.json
│   │   ├── expo-env.d.ts
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── README.md
│   └── admin/
│       ├── public/
│       ├── src/
│       │   ├── App.tsx
│       │   ├── main.tsx
│       │   └── ...
│       ├── index.html
│       ├── package.json
│       ├── tsconfig.json
│       └── vite.config.ts
├── packages/
│   └── .gitkeep
├── node_modules/
├── .gitignore
├── package.json
├── package-lock.json
└── README.md

第4章:開発時の基本操作

モノレポ構成での日常的な開発操作について説明しておきます。

1. 各アプリの起動方法

モノレポでは、ルートディレクトリからすべてのアプリを操作できます。
モバイルアプリを起動する場合は以下のコマンドを実行しましょう。

npm run mobile:start

管理画面を起動する場合は以下のコマンドを実行しましょう。

npm run admin:dev

両方を同時に起動したい場合は、ターミナルを2つ開いてそれぞれのコマンドを実行します。または、concurrently などのツールを使用して1つのコマンドで同時起動することも可能です。

2. パッケージの追加方法

特定のワークスペースにパッケージを追加する場合は、--workspace オプションを使用します。

npm install パッケージ名 --workspace=apps/admin

すべてのワークスペースで使用する開発ツール(ESLint、Prettier など)をルートに追加する場合は、-D(devDependencies)オプションと -W(workspace-root)オプションを使用します。

npm install -D -W eslint prettier

3. 共有パッケージの作成

将来、モバイルアプリと管理画面で共有するコードが増えてきた場合は、packages ディレクトリに共有パッケージを作成します。

mkdir -p packages/shared/src

packages/shared/package.json を作成します。

{
  "name": "@fittrack/shared",
  "version": "1.0.0",
  "private": true,
  "main": "./src/index.ts",
  "types": "./src/index.ts"
}

packages/shared/src/index.ts に共有するコードをエクスポートします。

export interface User {
  id: number;
  name: string;
  email: string;
}

export function formatDate(date: string): string {
  return new Date(date).toLocaleDateString('ja-JP');
}

各アプリから使用するには、まず依存関係として追加します。

npm install @fittrack/shared --workspace=apps/admin

そして、コード内でインポートします。

import { User, formatDate } from '@fittrack/shared';

おわりに

今回の記事では、『React Native Expo』と『Vite + React』を1つのリポジトリで管理するモノレポ構成について解説しました。


モノレポは、「コードの共有」「依存関係の一元管理」「一貫した開発体験」「アトミックなコミット」「効率的なリファクタリング」など、多くの利点を得ることができます。特に、モバイルアプリと管理画面のように、同じドメインを扱う複数のアプリケーションを開発する場合に効果を発揮するかと思います!

npm workspaces を使用することで、追加のツールをインストールすることなく、簡単にモノレポを構築することができます。既存のプロジェクトからの移行も、本記事で紹介した手順に従えば、Git の履歴を保持したまま構築することができるかと思います。


最後に、ここまで一読してくださりありがとうございました...!
もし他に、「こういう手法の方がより効率的に管理できるよ!」「このやり方の方が簡単!」などあれば、是非コメント等いただけますと幸いです☺️

Discussion