
この記事は、ゼスト Advent Calendar 2025の19日目の記事です。
こんにちは、株式会社ゼストでエンジニアをしている山下です。
弊社では訪問看護・介護事業所向けのスケジュール管理サービス「ZEST」を開発しています。先日、弊社のプロダクトの1つである ZEST HUB のiOS/Android向けスマホアプリをリリースいたしました。
従来はWebサービスの一部としてブラウザから使用する形態だったのですが、訪問看護・介護スタッフの利便性向上や、今後の更なる機能拡張を目的として、ネイティブアプリとして再設計・開発を行いました。
ネイティブアプリ化によって、ボタンタップ時の即座のフィードバック、スムーズな画面遷移、スワイプなどのジェスチャー操作など、Web では実現しにくい細かな体験の向上が得られました。訪問先で忙しく作業するスタッフにとって、こうした「サクサク感」は重要なポイントです。
本記事では、以前のブログ記事で紹介したFull-Stack TypeScriptの恩恵を、React Nativeでのモバイルアプリ開発でも最大限活用した事例をご紹介します。
開発の概要
HUBアプリの開発は画面設計やデザイン、テスト等含めて約半年かけて行いました。少人数での開発でしたが、これはチームが築いてきた Full-Stack TypeScript の基盤や共有パッケージ、そして Web 版 HUB の設計資産があってこそ実現できたものです。
また、近年の React Native エコシステムの進化や、Claude Code など生成AIツールの活用も大きな助けになりました。少人数で開発できたのは、こうした「巨人の肩に乗る」環境が整っていたからだと感じています。
採用した技術スタック
HUBアプリでは、以下の技術スタックを採用しました。
| カテゴリ | 技術 |
|---|---|
| フレームワーク | React Native + Expo SDK |
| 言語 | TypeScript |
| API通信 | tRPC + TanStack Query |
| スタイリング | react-native-unistyles v3 |
| ナビゲーション | Expo Router |
| ビルド・配信 | EAS Build / EAS Update |
なぜこの技術スタックを選んだのか
技術選定にあたっては、「既存資産の活用」と「長期的な保守性」を重視しました。
React Nativeを選んだ理由
弊社は Web エンジニアがメインで、Full-Stack TypeScript の環境が整っています。そのため、Swift/Kotlin によるネイティブ開発はほとんど選択肢にありませんでした。
また、TypeScriptが使えるモバイルアプリフレームワークもいくつかありますが、React Nativeのエコシステムが最も成熟していることや、前職でReact Nativeを使ったモバイル開発のリードエンジニアをしていたこともあり、React Nativeを採用しました。
Expoを選んだ理由
React Native 開発には、Expo を使う方法と bare workflow(素の React Native)で開発する方法がありますが、今回は Expo を採用しました。
Expo自体は以前から知っていて、元々はReactNativeをより簡単に扱うためのラッパーというイメージだったのですが、今回久々に調べてみるとさまざまなツールや機能が追加されていて、この数年でエコシステムが大きく進化していて驚きました。
特に便利だと感じたのは以下の3点です。
- CNG(Continuous Native Generation):
expo prebuildコマンドでネイティブプロジェクト(ios/android フォルダ)を自動生成できます。ネイティブコードを直接管理する必要がなくなり、app.config.tsだけで設定を完結できます - EAS Buildによるビルド: ビルドからストア申請、OTA アップデートまでを一貫した環境で管理できます。
- EAS UpdateによるOTAアップデート: ネイティブコードの変更を伴わないJavaScript/TypeScriptの更新であれば、ストア審査を経ずに即座にデプロイできます。これにより、従来のWebアプリケーションのリリースサイクルと同じタイミングでアプリのリリースができるようになりました。
CDに関してはFastlaneのようなツールとも比較したのですが、ワークフローの構築がとても簡単で、中長期的なメンテナンスコストも低いと感じました。
スタイリングの方針
今回のHUBアプリでは、オールインワンのUIライブラリは使わない方針としました。
TamaguiやReact Native PaperなどReact Native向けのUIライブラリをいくつか調査したのですが、WebのReact向けのライブラリと比べるとまだ成熟し切っていない印象でした。
一方で、標準の StyleSheet API にはテーマ管理やレスポンシブ対応などの機能が不足しています。そこで、その不足を補う目的でスタイリングライブラリの react-native-unistyles を導入しました。
unistyles の最大の特徴は StyleSheet API との1:1互換性です。書き方がほぼ同じなので、万が一ライブラリが使えなくなっても標準 API への移行が容易です。NativeWind(Tailwind 記法)や Restyle(props ベース)も検討しましたが、標準と異なる書き方になる可逆性の観点や、チーム内の好みの問題で不採用になりました。
Full-Stack TypeScriptの恩恵
tRPCによるAPI型共有がモバイルでも機能する
以前の記事で紹介した通り、ゼストではtRPCを使ってフロントエンドとバックエンドで型情報を共有しています。この仕組みは、Webアプリだけでなくモバイルアプリでもそのまま活用できます。
// Web版(Next.js)での使用例 import { useTRPCGetEvents } from "@zest/homecare-api-client"; const { data } = useTRPCGetEvents({ officeId, date }); // モバイル版(React Native)でも全く同じように使える import { useTRPCGetEvents } from "@zest/homecare-api-client"; const { data } = useTRPCGetEvents({ officeId, date });
このコードは完全に同一です。APIクライアントを @zest/homecare-api-client という共有パッケージにまとめているため、WebでもモバイルでもインポートするだけでAPIを呼び出せます。
共有パッケージの活用
モノレポ構成のメリットを活かし、以下のパッケージをWeb/モバイルで共通化しています。
| パッケージ | 役割 |
|---|---|
@zest/homecare-api-client |
tRPCクライアントとカスタムフック |
@zest/homecare-domain-values |
ドメイン固有の型・Enum定義 |
@zest/shared-lib |
汎用ユーティリティ関数 |
特に homecare-api-client パッケージには、APIの呼び出しロジックだけでなく、TanStack Queryとの連携やエラーハンドリングのパターンも含まれています。これにより、モバイルアプリ開発時に従来のWeb版で使っていたコード資産をそのまま流用できました。
ディレクトリ構成の統一
パッケージだけでなく、ディレクトリ構成もWeb版にできるだけ近い設計方針を採用しました。これは開発チームの認知負荷を下げ、既存の実装を参考にしやすい環境を作る意図があります。
Webのディレクトリ構成の詳細については、弊社の海老原が以下の記事で紹介しています。
Claude Codeによる開発効率化
ゼストの開発ではClaude Codeを導入しています。
既存のWeb版HUBと大まかな仕様が同じだったため、Web版のコードを参考にして開発を進められました。
これにより、Claude CodeによるAIコーディングの恩恵を最大限受けられました。
テンプレート駆動の量産アプローチ
Claude Code の活用で最も効果的だったのは、「テンプレートを作って量産する」アプローチです。
例えば「予定詳細」画面では、予定の種類ごとに微妙に表示内容が異なるため、予定の種類ごとに画面を作成しています。
これを作成する際に、最初の1画面を自分で丁寧に実装し、それをテンプレートとして Claude Code に「この画面をベースに、B画面、C画面、D画面を作って」と依頼します。すると、既存のコードパターンを理解した上で、適切にバリエーションを生成してくれました。同時にWeb版のコードも参考にさせたことで、ロジック周りなども適切に生成してくれました。
コンポーネントのバリアント作成でも同様です。例えばカードコンポーネントの基本形を作った後、「このカードの色違いバリアントを追加して」と依頼すると、既存のテーマ定義を参照しながら一貫性のあるコードを生成してくれました。

現実的な限界も理解しておく
実はゼロから全てを Claude Code に任せるのも試したのですが、結果的にはうまくいきませんでした。
現在のAIツールは「既存のパターンを理解して横展開する」ことは得意ですが、「プロジェクト固有の設計判断を下す」ことはまだまだ人間の役割です。 Full-Stack TypeScript によって型情報が共有されていることで、AI が正確なコードを書きやすくなっているのは間違いありませんが、それでも「土台作り」はまだまだ人間の役割かなと感じました(プロンプトや、AIの成長の時間の問題かもしれませんが😅)。
結果として、Claude Code は「量産フェーズの効率化ツール」として非常に有効でした。Full-Stack TypeScript によるコード資産と、AI ツールの組み合わせが、少人数でもモバイルアプリ開発を可能にした大きな要因です。
スタイリングの実践
unistylesの実際の活用例
前述の通り、スタイリングには react-native-unistyles を採用しました。
使い方はReact Native標準のStyleSheet APIとほぼ同じです。
// ProfileSection.tsx import { StyleSheet } from "react-native-unistyles"; export const ProfileSection = ({ profile, email }: Props) => { return ( <View style={styles.root}> <Text style={styles.userName}>{profile.familyName} {profile.givenName}</Text> <Text style={styles.userEmail}>{email}</Text> </View> ); }; const styles = StyleSheet.create((theme) => ({ root: { backgroundColor: theme.baseColor.white, borderRadius: theme.radius.lg, paddingVertical: 8, }, userName: { fontSize: 18, fontWeight: "600", color: theme.textColor.basic, }, userEmail: { fontSize: 14, color: theme.textColor.sub, }, }));
テーマ定義は別ファイルで管理し登録しておくことで、第一引数でテーマを取得できるようになっています。
これにより、標準のStyleSheet APIと同じインタフェースを使いながらスタイルの変数やMediaQueryなども使うことができるようになります。
課題と注意点
Full-Stack TypeScript のメリットは大きいですが、モバイル展開には課題や注意点もありました。
UIデザインは共有できない
Web とモバイルでは、UIの思想が根本的に異なります。
| 観点 | Web | モバイル |
|---|---|---|
| 画面サイズ | 大画面、複数要素を同時表示 | 小画面、1画面1機能が基本 |
| 入力方法 | マウス、キーボード | タッチ、ジェスチャー |
| ナビゲーション | URLベース | スタック・タブベース |
結果として、UIデザインはほぼ全て作り直しになりました。ロジックやAPIクライアントは共有できても、見た目の部分は新規に設計・実装する必要がありました。この辺りはデザイナーと何度もコミュニケーションを取りながら進めました。
ネイティブ特有の考慮事項
Web とは違った、モバイル特有の考慮事項も多くありました。
- セレクトボックスが無い: Web では当たり前に使うセレクトボックスですが、モバイルでは使わないことが多い
- 日付選択UI: Web の DatePicker とは異なり、iOS/Android それぞれのネイティブピッカーを使う
- アプリの状態管理: フォアグラウンド、バックグラウンド、終了状態を考慮した実装が必要
- オンライン・オフラインの考慮: インターネット接続がない場所での動作や、データの同期処理
これらは TypeScript の型共有とは関係なく、ネイティブアプリ開発の知識が必要になる領域です。
型推論のパフォーマンス問題
これはモバイルというより、規模の大きくなったFull-Stack TypeScript環境で起きている課題です。
tRPC と Zod を組み合わせた型システムは非常に強力ですが、型推論が重くなりがちです。具体的には、IDE(VS Code)のオートコンプリートや型ヒント表示が遅くなることがあります。
弊社の場合、Zod のバージョンが古いことも一因でした。
Zod v4については、弊社の正原が検証記事をあげています。
Zod v4のアップデート後や、今後tsgoを活用することで、この問題が改善されることを期待しています。
リリースサイクルの非同期性
ゼストでは週1回の頻度でリリースを行っています。 EAS Updateを使うことで、ネイティブコードの変更を伴わないJSレイヤーの更新であれば、ストア審査を経ずに即座にデプロイすることができます。 これにより、従来のWeb版のフロント/バックエンドと同じリリースサイクルを維持できるようになりました。
しかしモバイルアプリは、React Native 本体や Expo SDK など、ネイティブモジュールを含むライブラリのインストールやアップデート時はストア審査が必要になります。
これにより、以下のような運用上の考慮が必要になります:
- アップデートタイミング: React Native や Expo のアップデートを、いつ・どのように行うかの計画
- ブランチ戦略: ストア審査が必要になった場合は、ブランチを分けて管理する必要がある
まとめ
React NativeでもtRPCを活用したFull-Stack TypeScript開発は十分に可能であり、大きなメリットがあります。
コード資産の活用: 共有パッケージにより、ドメインの定義や便利関数など様々な資産を活用
AI支援との相性: 型情報や類似する仕様の既存コードがあることで、AIツールがより正確なコードを生成
少人数開発の実現: 少人数チームでのリリースを達成
すでに TypeScript + tRPC で Web アプリを開発しているチームにとっては、モバイル展開のハードルが大きく下がると感じました。
近年のExpoエコシステムの進化と組み合わせることで、「Webと同じ感覚でモバイルアプリを開発・運用する」という体験に近づけたと感じています。
これからFull-Stack TypeScriptでモバイルアプリ開発を検討している方の参考になれば幸いです。