RORK LABEN
TOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しましたTOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しました
記事一覧/開発ツール
開発ツール/2026-03-13中級

Rorkアプリのナビゲーションを組み直さずに済ませる — Expo Router の設計手順

Rorkが生成するExpo Routerのファイル構造を先に理解し、タブ・スタック・モーダル・認証ガード・ディープリンクを手戻りなく積み上げる手順を、実際につまずいた箇所とあわせて整理します。

Expo Router5ナビゲーション3React Native209Rork515ディープリンク8タブバースタックナビゲーション

Rorkアプリのナビゲーションを組み直さずに済ませる — Expo Router の設計手順

個人開発でRork製のアプリを育てていると、画面数が増えた段階でほぼ必ずナビゲーションの作り直しに直面します。私自身、最初はタブ4つで足りていた構成が、詳細画面と設定モーダルを足すうちに遷移の整理がつかなくなり、一度まるごと組み直したことがありました。原因はシンプルで、生成されたコードを読む前に「どのファイルがどの画面に対応するのか」を把握していなかったことです。

Rorkが生成するReact Native / Expoアプリには、Expo Routerというファイルベースのルーティングシステムが採用されています。この仕組みを先に理解しておくと、Rorkへ渡すプロンプトの精度が変わり、後からの作り直しもぐっと減ります。タブ・スタック・モーダルの実装から認証ガード・ディープリンクまで、私が個人開発の現場で実際に手を動かしている順序で整理していきます。

Expo Routerとは?

Expo Routerはファイルシステムをそのままルート定義として使う、Next.jsライクなルーティングライブラリです。app/ ディレクトリ内のファイル構造がそのままURLパスとナビゲーション構造になります。

Rorkが生成するアプリはすでにExpo Routerが設定済みですが、「どのファイルがどの画面に対応するか」を理解しておくと、Rorkへのプロンプトの精度が大幅に向上します。

ディレクトリ構造の基本

app/
├── _layout.tsx          ← ルートレイアウト(共通設定)
├── index.tsx            ← ホーム画面(/)
├── (tabs)/              ← タブナビゲーション
│   ├── _layout.tsx      ← タブ定義
│   ├── home.tsx         ← ホームタブ
│   ├── explore.tsx      ← 探索タブ
│   └── profile.tsx      ← プロフィールタブ
├── (auth)/              ← 認証グループ
│   ├── login.tsx
│   └── signup.tsx
└── modal.tsx            ← モーダル画面

カッコ () で囲まれたフォルダはグループと呼ばれ、URLには含まれませんが、レイアウトをネストするために使います。ここは最初に必ず腹落ちさせておきたい箇所です。(tabs)/home/[id].tsx の実際のパスは /tabs/home/123 ではなく /home/123 です。私はここを勘違いしたまま router.push('/tabs/home/1') と書き、遷移が無反応になる原因を半日探しました。ディレクトリ名とURLが一致しない唯一の例外がグループだと覚えておくと、以降の遷移コードで迷いません。


タブナビゲーションの実装

スマホアプリで最もよく使われる画面下部のタブバー。Rorkに対して以下のようなプロンプトを書くと、正しいExpo Router形式のタブナビゲーションが生成されます。

Rork向けプロンプト例:

ホーム、検索、お気に入り、プロフィールの4タブを持つ
アプリを作成してください。
各タブにはアイコン(Ionicons)と日本語ラベルを表示してください。

生成される (tabs)/_layout.tsx の典型的な構造:

import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
 
export default function TabLayout() {
  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: '#007AFF',
        tabBarInactiveTintColor: '#8E8E93',
        headerShown: false,
      }}
    >
      <Tabs.Screen
        name="home"
        options={{
          title: 'ホーム',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="home" size={size} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="search"
        options={{
          title: '検索',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="search" size={size} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="favorites"
        options={{
          title: 'お気に入り',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="heart" size={size} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="profile"
        options={{
          title: 'プロフィール',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="person" size={size} color={color} />
          ),
        }}
      />
    </Tabs>
  );
}

スタックナビゲーションの実装

詳細画面やフォームのような「進む・戻る」操作が必要な画面にはスタックナビゲーションを使います。

タブ内にスタックをネストする

app/
└── (tabs)/
    ├── _layout.tsx
    └── home/
        ├── index.tsx        ← ホーム一覧(/home)
        └── [id].tsx         ← 詳細画面(/home/123)

動的ルート([id].tsx)を使うと、パラメータを含む画面を作れます。

// app/(tabs)/home/[id].tsx
import { useLocalSearchParams } from 'expo-router';
import { View, Text } from 'react-native';
 
export default function DetailScreen() {
  const { id } = useLocalSearchParams<{ id: string }>();
 
  return (
    <View style={{ flex: 1, padding: 16 }}>
      <Text>アイテムID: {id}</Text>
    </View>
  );
}

詳細画面への遷移

import { router } from 'expo-router';
 
// 押したときに詳細画面へ遷移
<TouchableOpacity onPress={() => router.push(`/home/${item.id}`)}>
  <Text>{item.title}</Text>
</TouchableOpacity>

Rork向けプロンプト例:

ニュース一覧画面から記事詳細画面に遷移するアプリを作ってください。
詳細画面にはニュースのIDをURLパラメータで渡し、
戻るボタンで一覧に戻れるようにしてください。

モーダル画面の実装

設定画面や確認ダイアログなど、全画面モーダルが必要な場合のパターンです。

// app/_layout.tsx(ルートレイアウト)
import { Stack } from 'expo-router';
 
export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      <Stack.Screen
        name="modal"
        options={{
          presentation: 'modal',
          title: '設定',
        }}
      />
    </Stack>
  );
}

モーダルを開くには:

import { router } from 'expo-router';
 
<TouchableOpacity onPress={() => router.push('/modal')}>
  <Text>設定を開く</Text>
</TouchableOpacity>

認証ガードの実装

未ログインユーザーをログイン画面にリダイレクトするパターンは、Rorkでよく必要になります。

// app/_layout.tsx
import { useEffect } from 'react';
import { Slot, useRouter, useSegments } from 'expo-router';
import { useAuth } from '@/hooks/useAuth';
 
export default function RootLayout() {
  const { user, loading } = useAuth();
  const segments = useSegments();
  const router = useRouter();
 
  useEffect(() => {
    if (loading) return;
 
    const inAuthGroup = segments[0] === '(auth)';
 
    if (!user && !inAuthGroup) {
      // 未ログインなのに保護されたページにいる場合
      router.replace('/(auth)/login');
    } else if (user && inAuthGroup) {
      // ログイン済みなのに認証ページにいる場合
      router.replace('/(tabs)/home');
    }
  }, [user, loading, segments]);
 
  return <Slot />;
}

Rork向けプロンプト例:

ログインしていないユーザーはログイン画面にリダイレクトされ、
ログイン後はホーム画面に遷移するアプリを作ってください。
認証状態はZustandで管理してください。

ディープリンクの設定

外部URLやプッシュ通知からアプリの特定画面を開くには、ディープリンクの設定が必要です。

app.jsonの設定

{
  "expo": {
    "scheme": "myapp",
    "android": {
      "intentFilters": [
        {
          "action": "VIEW",
          "autoVerify": true,
          "data": [
            {
              "scheme": "https",
              "host": "myapp.com",
              "pathPrefix": "/item"
            }
          ],
          "category": ["BROWSABLE", "DEFAULT"]
        }
      ]
    }
  }
}

Expo Routerはこの設定を元にディープリンクを自動的に処理します。たとえば myapp://home/123 というURLを開くと、app/(tabs)/home/[id].tsx(id=123)が表示されます。

💡
Rorkでディープリンクを設定するには「プロンプトに`scheme: myapp`を使ってディープリンクを設定し、/item/:idパスに対応させてください」と指示すると、app.jsonとルート設定を自動的に更新してくれます。

マウント前に遷移して落ちるとき

認証ガードを入れた直後によく踏むのが、Attempted to navigate before mounting the Root Layout component というエラーです。useEffect の中で router.replace() を呼んでいるのに、ルートレイアウトのナビゲータがまだ組み上がっていない、という状態で発生します。

私の場合、認証状態の復元を AsyncStorage から読む処理が速すぎて、初回描画の前に遷移が走ったのが原因でした。対処は二段構えにしています。

// app/_layout.tsx
import { useEffect } from 'react';
import { Slot, useRouter, useSegments, useRootNavigationState } from 'expo-router';
import * as SplashScreen from 'expo-splash-screen';
import { useAuth } from '@/hooks/useAuth';
 
SplashScreen.preventAutoHideAsync();
 
export default function RootLayout() {
  const { user, loading } = useAuth();
  const segments = useSegments();
  const router = useRouter();
  const navState = useRootNavigationState();
 
  useEffect(() => {
    // ① ナビゲータのマウント完了を待つ
    if (!navState?.key) return;
    // ② 認証状態の復元完了を待つ
    if (loading) return;
 
    const inAuthGroup = segments[0] === '(auth)';
 
    if (!user && !inAuthGroup) {
      router.replace('/(auth)/login');
    } else if (user && inAuthGroup) {
      router.replace('/(tabs)/home');
    }
 
    SplashScreen.hideAsync();
  }, [navState?.key, user, loading, segments]);
 
  return <Slot />;
}

useRootNavigationState() が返す key は、ナビゲータが実際に組み上がったあとに初めて値を持ちます。これを見張ってから遷移すると、起動直後のクラッシュがなくなります。あわせてスプラッシュを手動で閉じることで、ログイン画面が一瞬だけちらつく現象も消えました。

もう一点。認証の判定ロジックそのものをどこに置くかは、Rork でユーザー認証を実装する — Firebase・Supabase 連携 の内容とあわせて考えると整理しやすいはずです。ナビゲーション層は「判定結果を受け取って行き先を決めるだけ」に保つのが、後から認証基盤を差し替えるときに効いてきます。


見つからない画面を先に置いておく

ディープリンクを有効にすると、タイプミスや古いURLでアプリが開かれる場面が必ず出てきます。Expo Router には +not-found.tsx という予約ファイル名があり、どのルートにも一致しなかったときにここが表示されます。

// app/+not-found.tsx
import { Link, Stack } from 'expo-router';
import { View, Text } from 'react-native';
 
export default function NotFoundScreen() {
  return (
    <>
      <Stack.Screen options={{ title: 'お探しの画面が見つかりません' }} />
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', padding: 24 }}>
        <Text style={{ marginBottom: 16 }}>
          リンクが古いか、画面が移動した可能性があります。
        </Text>
        <Link href="/(tabs)/home" replace>
          <Text style={{ color: '#007AFF' }}>ホームに戻る</Text>
        </Link>
      </View>
    </>
  );
}

ここで Linkreplace を付けているのは、履歴に「見つからない画面」を残さないためです。付け忘れると、ホームに戻ったあとに端末のバックジェスチャーでまたエラー画面に戻ってしまいます。細かい話ですが、実機で触ると気持ち悪さがすぐ分かります。

ディープリンクの動作確認は、ビルドし直さなくてもターミナルから叩けます。

# iOS シミュレータ
xcrun simctl openurl booted "myapp://home/123"
 
# Android エミュレータ
adb shell am start -W -a android.intent.action.VIEW -d "myapp://home/123"

存在しないIDを渡して +not-found に落ちること、正しいIDで詳細画面が開くこと。この2つを確認してから申請に進むと、レビューでのリジェクトを一つ減らせます。


ナビゲーション設計のベストプラクティス

Rorkで高品質なナビゲーション設計を実現するための5つのポイントを紹介します。

1. 最初にナビゲーション構造を決める

アプリを作り始める前に、どの画面がどのタブに属するか、どの画面がモーダルで開くかを明確にしてからプロンプトを書きましょう。

2. プロンプトで画面遷移を具体的に書く

「一覧から詳細に遷移」「バックボタンで戻る」「ログイン後はホームへ」など、遷移の流れを明示的に伝えると生成精度が上がります。プロンプトの粒度そのものについてはRork に渡すプロンプトの書き方 — アプリ生成の精度を高める7つのコツも参考になります。

3. グループを使ってレイアウトを整理する

認証フロー用の (auth)/、メインアプリ用の (tabs)/、管理画面用の (admin)/ のようにグループを活用すると、ファイル構造が整理されます。

4. router.pushrouter.replace を使い分ける

  • router.push() → 履歴に追加(戻るボタンで戻れる)
  • router.replace() → 履歴を置き換え(ログイン後のリダイレクトなど)
  • router.back() → 前の画面に戻る

5. ヘッダーを一箇所で管理する

各画面で個別にヘッダーを設定せず、_layout.tsxscreenOptions でスタイルを一元管理しましょう。


次に触るなら、この順番で

冒頭で触れた「まるごと組み直し」を避けられるようになったのは、画面を足す前にディレクトリ構造を紙に書き出す習慣がついてからでした。グループはURLに出ない。遷移はマウント後。行き先が無いときの受け皿を先に置く。この3つを最初に押さえておくだけで、手戻りの大半は消えます。

今日から動かすなら、app/ を開いてグループの入れ子を確認し、+not-found.tsx を1枚追加してみてください。それだけで、ディープリンクを本気で設定する準備が整います。

タブから始めて、必要になった順にスタックとモーダルを重ねる。私はこの積み方に落ち着きました。実装の参考になれば幸いです。

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

開発ツール2026-03-27
Rork Max 高度なナビゲーション設計パターン — ネスト構造・カスタム遷移・認証フロー・共有要素トランジションの実装
Rork Max のナビゲーション設計を、2013年から個人で運用してきたアプリ群の実体験ベースで掘り下げる上級ガイド。Expo Router のネスト構造、認証フロー分岐、共有要素トランジションに加え、本番運用で気づいた6つの落とし穴と回避策を動くコードで紹介します。
開発ツール2026-07-15
開発中は黄色い警告だけだったのに、復帰したら落ちた — ルートパラメータに関数を入れていた話
Rork が生成した画面遷移コードが、ルートパラメータに関数と Date を載せていました。開発中は警告で済むのに、アプリを復帰させると params.onFavorite is not a function で落ちます。原因と、ID だけを渡す形への直し方をまとめました。
開発ツール2026-07-15
「、」が行頭に落ちた — Rork が組んだ名言カードと日本語の禁則処理
React Native の Text は日本語の行頭禁則を保証しません。実測した違反率、WORD JOINER による事前結合の実装、onTextLayout での自動監査、Rork Max(SwiftUI)との挙動差までまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →