Claude CodeでReact Native開発: Expo/ネイティブ/ビルド検証の実践ガイド
Claude CodeでReact Nativeを安全に進める手順。Expo選定、権限、Metro、端末テスト、リリース確認を実例コードで解説。
Claude CodeにReact Nativeを任せる前に決めること
React Native開発でClaude Codeを使う価値は、画面を一枚速く作ることだけではありません。iOS/Androidで違う権限、Metroの解決エラー、ネイティブ再ビルド、アクセシビリティ、リリース確認までを同じ作業単位に入れられることが大きいです。
ただし「React Nativeアプリを作って」だけでは、Expo Goで足りるのか、development buildが必要なのか、bare React Nativeでネイティブコードを触るのかが曖昧です。最初にproject map、許可コマンド、検証端末を渡します。
この記事では、Expo/bareの選び方、権限設計、カメラ画面、Metro対応、端末検証、リリース前チェックまでを一つの流れにします。Claude Codeは overview と permissions、Expoは Expo Documentation と development builds、React Nativeは Environment setup と Native Modules、Metroは Troubleshooting、アクセシビリティは Accessibility を基準にします。
Claude Codeそのものの導入がまだなら、先にClaude Code入門ガイドを読み、チーム運用ではClaude Code権限設定ガイドも合わせて整えてください。
まずproject mapを作る
project mapとは、エージェントに読ませる「アプリの地図」です。harness(エージェントの足場)として、ディレクトリ、実行コマンド、触ってよい範囲、確認端末、リリース方式を先に固定します。これがないと、動く差分は出てもネイティブビルドや審査の文脈を落としやすくなります。
flowchart LR
A["Project map"] --> B["Claude Code task"]
B --> C["JS/TS implementation"]
B --> D["Native config"]
C --> E["Metro and unit tests"]
D --> F["Dev build / emulator"]
E --> G["Accessibility check"]
F --> G
G --> H["Release checklist"]
最初にClaude Codeへ渡すメモは、次のように短くて構いません。
# React Native task map
App type: Expo app using TypeScript and Expo Router.
Native runtime: Expo Go for pure JS changes, development build for native libraries.
Targets: Android emulator first, iOS simulator on macOS before release.
Allowed files: app/, components/, hooks/, app.config.ts, metro.config.js, __tests__/.
Do not change: package manager, app slug, bundle identifiers, signing files, .env files.
Verification: npm run lint, npm test, npx expo-doctor, Android emulator smoke test.
Handoff: list changed files, commands run, platform not tested, and any native rebuild needed.
新規Expoでは create-expo-app がAI coding agents向けの文脈を用意する流れもあります。既存プロジェクトでは古いREADMEや未整理のネイティブ設定が混ざるため、毎回現物を読ませます。
Expoかbare React Nativeかを先に決める
React Native公式ドキュメントは、フレームワーク利用とAndroid Studio/Xcodeを直接扱う場合を分けています。新規はExpoが速く、既存SDK、独自Gradle、複雑なPod設定があるならbare React Nativeが現実的です。
| 判断軸 | Expoが向くケース | bare React Nativeが向くケース |
|---|---|---|
| 初速 | 新規MVP、社内アプリ、学習用、Expo SDKで足りる機能 | 既存iOS/Android資産を流用する |
| ネイティブ機能 | config pluginで設定できるカメラ、SecureStore、通知など | 独自SDK、決済端末、特殊なBluetooth、細かいビルド設定 |
| Claude Codeへの頼み方 | app/、components/、app.config.ts中心に小さく依頼 | ios/、android/、Codegen、Pod、Gradleの差分確認まで依頼 |
| 検証 | Expo Goまたはdevelopment build、npx expo-doctor | npm run android、pod install、Xcode/Android Studioでビルド |
Expo Goを万能の実機確認環境だと思わないことも重要です。Expo Goは固定されたネイティブライブラリを持つ環境です。ネイティブコードを含むライブラリを追加したら、development buildを作り直す前提で進めます。
最初のセットアップは次のようにします。
npx create-expo-app@latest rn-claude-lab
cd rn-claude-lab
npx expo install expo-camera expo-secure-store @react-native-community/netinfo
npm install --save-dev @testing-library/react-native jest-expo @types/jest
npx expo start
Android EmulatorならExpoの開発サーバーで a、iOS SimulatorならmacOS上で i を押して起動します。ネットワークが厳しい環境では npx expo start --tunnel も使えますが、遅くなるので通常はLANかエミュレーターを優先します。
Claude Codeの権限をモバイル向けに絞る
React Nativeではコマンド実行の副作用が大きいです。expo prebuild、pod install、gradlew clean、eas build は必要な場面がありますが、軽いUI修正で走るとレビュー対象が広がります。
Claude Codeのpermissionsはallow、ask、denyで分けられます。プロジェクト共有の .claude/settings.json では、読み取りや安全な検証コマンドは許可し、ネイティブ再生成やリリースビルドは確認を挟むのが扱いやすいです。
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Bash(npm test)",
"Bash(npm run lint)",
"Bash(npx expo-doctor)",
"Bash(adb devices)"
],
"ask": [
"Edit",
"Bash(npx expo prebuild*)",
"Bash(eas build*)",
"Bash(cd ios && bundle exec pod install)"
],
"deny": [
"Read(.env)",
"Read(.env.local)",
"Bash(git push*)",
"Bash(rm -rf*)"
]
}
}
この設定は、モバイル特有の広い副作用を作業単位に閉じ込めるためのものです。詳しくはClaude Code権限設定ガイドも参照してください。
カメラ権限つき画面を実装する
1つ目の実用例は、イベント受付や在庫管理で使うQRコード読み取りです。Expo Cameraでは CameraView と useCameraPermissions を使えます。Claude Codeには「未判定、拒否、許可済みを分ける」「アクセシビリティラベルを付ける」「スキャン後は重複発火を止める」と明示します。
// components/CameraQrCheck.tsx
import { CameraView, useCameraPermissions } from 'expo-camera';
import { useState } from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
type ScanResult = {
data: string;
type: string;
} | null;
export function CameraQrCheck() {
const [permission, requestPermission] = useCameraPermissions();
const [scan, setScan] = useState<ScanResult>(null);
if (!permission) {
return (
<View style={styles.center}>
<Text>Checking camera permission...</Text>
</View>
);
}
if (!permission.granted) {
return (
<View style={styles.center}>
<Text style={styles.title}>Camera access is required</Text>
<Text style={styles.body}>
Allow camera access to scan QR codes on this device.
</Text>
<Pressable
accessibilityRole="button"
accessibilityLabel="Allow camera access"
disabled={!permission.canAskAgain}
onPress={requestPermission}
style={({ pressed }) => [
styles.button,
pressed && styles.buttonPressed,
!permission.canAskAgain && styles.buttonDisabled,
]}
>
<Text style={styles.buttonText}>Allow camera</Text>
</Pressable>
</View>
);
}
return (
<View style={styles.container}>
<CameraView
style={styles.camera}
barcodeScannerSettings={{ barcodeTypes: ['qr'] }}
onBarcodeScanned={
scan
? undefined
: ({ data, type }) => {
setScan({ data, type });
}
}
/>
<View style={styles.result}>
<Text accessibilityLiveRegion="polite" style={styles.title}>
{scan ? `Scanned ${scan.type}` : 'Point the camera at a QR code'}
</Text>
{scan ? <Text selectable>{scan.data}</Text> : null}
{scan ? (
<Pressable
accessibilityRole="button"
accessibilityLabel="Scan another QR code"
onPress={() => setScan(null)}
style={styles.button}
>
<Text style={styles.buttonText}>Scan again</Text>
</Pressable>
) : null}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#111827' },
center: { flex: 1, justifyContent: 'center', gap: 12, padding: 24 },
camera: { flex: 1 },
result: { gap: 12, padding: 16, backgroundColor: '#f9fafb' },
title: { fontSize: 18, fontWeight: '700', color: '#111827' },
body: { fontSize: 15, lineHeight: 22, color: '#374151' },
button: {
alignItems: 'center',
borderRadius: 8,
backgroundColor: '#2563eb',
paddingHorizontal: 16,
paddingVertical: 12,
},
buttonPressed: { opacity: 0.75 },
buttonDisabled: { backgroundColor: '#9ca3af' },
buttonText: { color: '#ffffff', fontWeight: '700' },
});
このコードはExpoプロジェクトに貼り付けて使えます。ただし、権限文言やネイティブ設定を変える場合はJavaScriptの再読み込みだけでは足りないため、app.config.ts に意図を残します。
// app.config.ts
import type { ConfigContext, ExpoConfig } from 'expo/config';
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
name: config.name ?? 'rn-claude-lab',
slug: config.slug ?? 'rn-claude-lab',
ios: {
...config.ios,
bundleIdentifier: 'com.example.rnclaudelab',
infoPlist: {
...config.ios?.infoPlist,
NSCameraUsageDescription: 'Scan QR codes for check-in.',
},
},
android: {
...config.android,
package: 'com.example.rnclaudelab',
permissions: ['CAMERA'],
},
plugins: ['expo-camera'],
});
Expoのconfig pluginsは、prebuild時にネイティブ設定を反映する仕組みです。「app.config.ts を変えたらdevelopment buildが必要かを報告して」と指示すると、Expo Goと配布ビルドのずれを減らせます。
Metroエラーはキャッシュだけで片付けない
2つ目の実用例は、Metroの Unable to resolve module やmonorepoでの依存解決エラーです。Metro公式は --reset-cache を案内していますが、毎回キャッシュ削除だけで済ませると根本原因を見落とします。
Claude Codeへの良い依頼は、エラー全文、発生コマンド、パッケージマネージャー、monorepo構成、直前に移動したファイルをセットで渡すことです。
// metro.config.js
const path = require('path');
const { getDefaultConfig } = require('expo/metro-config');
const projectRoot = __dirname;
const workspaceRoot = path.resolve(projectRoot, '..');
const config = getDefaultConfig(projectRoot);
config.watchFolders = [workspaceRoot];
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, 'node_modules'),
path.resolve(workspaceRoot, 'node_modules'),
];
module.exports = config;
この設定は、Expoアプリがワークスペースルートの依存関係を参照する構成向けです。普通の単一Expoアプリなら不要です。Claude Codeには「なぜ watchFolders が必要か、不要なら追加しないで」と頼みます。
よくある失敗は、cache resetで直ったように見えてimportパスの大小文字違いが残るケース、iOSだけ通るファイル名をAndroidで壊すケース、古い react-native link 前提を現在のautolinkingやExpo config pluginに持ち込むケースです。
ネイティブモジュールは「小さな仕様」から頼む
3つ目の実用例は、社内SDKやOS機能をReact Nativeへつなぐ場面です。React Native公式のNative ModulesはTurbo Native ModulesとCodegenを中心に説明されています。いきなりKotlin、Swift、TypeScriptを書かせず、まずTypeScript側の仕様を小さく固定します。
SecureStore、カメラ、NetInfoのようにExpo SDKで足りるなら、独自ネイティブモジュールは作りません。決済端末、特殊Bluetooth、社内認証SDKのように既存ライブラリがない場合だけ検討します。
Claude Codeへの依頼例はこうです。
Implement a native bridge plan, not the full code yet.
Goal: expose a device serial reader to TypeScript.
First output:
1. TypeScript interface and error model.
2. Android/iOS files that would need edits.
3. Build commands for each platform.
4. Risks: permissions, threading, simulator limitations, release signing.
Do not edit ios/ or android/ until the plan is reviewed.
この「計画だけ」の依頼は地味ですが、ネイティブ差分はレビューコストが高いため、先に境界を決める方が速くなります。
テストとアクセシビリティを同じ作業に入れる
React NativeのアクセシビリティAPIは、VoiceOverやTalkBackへ情報を渡すために使います。画面実装を頼むときは、ラベル、ボタンの役割、状態変化の読み上げ、タップ領域を完了条件に入れます。
単体テストでは、権限がない状態のUIをまず固定します。
// __tests__/CameraQrCheck.test.tsx
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react-native';
import { CameraQrCheck } from '../components/CameraQrCheck';
const mockRequestPermission = jest.fn();
jest.mock('expo-camera', () => ({
CameraView: 'CameraView',
useCameraPermissions: () => [
{ granted: false, canAskAgain: true },
mockRequestPermission,
],
}));
describe('CameraQrCheck', () => {
beforeEach(() => {
mockRequestPermission.mockClear();
});
it('requests camera permission from the empty state', () => {
render(<CameraQrCheck />);
fireEvent.press(screen.getByText('Allow camera'));
expect(mockRequestPermission).toHaveBeenCalledTimes(1);
});
});
テストだけで実機品質は保証できません。端末で権限ダイアログ、拒否後の導線、画面回転、暗所スキャン、TalkBack/VoiceOverを確認し、「Androidは検証済み、iOSは未検証」のように未確認範囲も書かせます。
リリース前チェックをコマンド化する
React Native Debugging docsでは、Dev MenuやLogBoxはrelease buildでは無効になると説明されています。開発中に見えた警告やネットワーク状態は、リリースビルドでは違う見え方になります。
Claude Codeには、実装完了だけでなく次のコマンド結果をまとめて返すように依頼します。
npm run lint
npm test -- --runInBand
npx expo-doctor
npx expo start -c
adb devices
adb shell input keyevent 82
npx expo run:android
# macOS only:
npx expo run:ios
EAS Buildを使う場合は、development、preview、production を分けます。Claude Codeには「productionプロファイルを勝手に変更しない」「bundleIdentifierとandroid.packageを変えない」「署名やストア提出は人間確認」と明示します。
3つの現場ユースケース
1つ目は、新規ExpoアプリのMVPです。ログイン、QR読み取り、簡単なオフライン保存、API連携なら、画面、hook、テスト、Expo設定を小さく分けて依頼します。収益導線があるなら計測イベントとCTAも完了条件に入れます。
2つ目は、既存bare React Nativeアプリの不具合修正です。Metroエラー、Androidだけ落ちる画面、iOSだけ権限文言が出ない問題では、「エラー全文を分類」「JSだけで直るか判定」「差分を最小化」と頼みます。
3つ目は、ネイティブSDK導入前の調査です。決済、健康データ、Bluetooth、社内認証では、公式ドキュメント、対応OS、権限、審査リスク、テスト端末を表にしてから進めます。レビュー用チェックリストやClaude CodeでTDDも使えます。
具体的な落とし穴
よくある落とし穴は4つです。Expo Goで動いた結果をリリース品質と誤解すること。iOS/Android差分を最後に回すこと。Metro cache resetを万能薬にすること。アクセシビリティを後付けにすることです。カメラ、位置情報、ファイル保存、戻るボタンは差が出やすいため、Platform分岐の理由も書かせます。Claude Codeアクセシビリティ対応もレビュー観点の補強になります。
CTA: モバイル開発フローを固定する
React NativeでClaude Codeを使うなら、project map、権限設定、検証コマンド、レビュー観点をテンプレート化した方が安定します。個人なら無料チートシート、実装プロンプトや設定例は商品一覧、チームでExpo/bare、権限、CI、リリース確認まで整えるならClaude Code研修・導入相談が向いています。
関連して、React開発とFlutter/Dart開発も参考になります。
この記事で紹介した内容を実際に試した結果、Masaの検証では「Expoかbareかを先に決める」「app.config.ts 変更時にdevelopment buildの要否を書かせる」「Android Emulatorで権限とMetroを先に確認する」の3点がもっとも効果的でした。カメラやSecureStoreのようなネイティブ機能は、コード生成よりも検証条件を先に固定した方が差し戻しが少なくなります。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
ObsidianメモをCLAUDE.mdに変えるClaude Code運用: 文脈を毎回説明しない仕組み
Obsidianの作業メモからCLAUDE.md用の運用ノートを作り、Claude Codeに安定した文脈を渡す方法。
Claude Code Revenue CTA Routing: 記事からPDF、Gumroad、相談へ送る設計
PVだけで終わらせず、読者の状態に合わせて無料PDF、Gumroad教材、導入相談へ分岐するCTA設計です。
Claude Codeチーム引き継ぎルール: レビュー、権限、収益導線まで渡す実務手順
Claude Codeの作業をチームで渡すための証拠、権限、ロールバック、無料PDF/Gumroad/相談導線の実務ルール。