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-06-09中級

Rork Max でディープリンクを実装する — Universal Links と URL Schemes

Rork Max アプリにディープリンクを追加する実装ガイド。URL Scheme と Universal Links の使い分け、AASA/assetlinks の落とし穴、コールド起動の取りこぼしを実例つきで解説します。

rork58deep-linksuniversal-linksreact-native12tutorial

タップ一つで「目的の画面」まで運ぶ

プッシュ通知を送ったのに、開いてみるとホーム画面に戻されてしまう。せっかく「今日の壁紙が届きました」と知らせても、ユーザーがその壁紙までたどり着く前に離脱してしまう — これは私自身が壁紙アプリの運営で何度も経験した、もったいない取りこぼしでした。

ディープリンクは、この距離をゼロにする仕組みです。myapp://wallpaper/sky-blue のような URL をタップすると、アプリが起動して、その壁紙の詳細画面が直接開きます。通知・Web サイト・他アプリ・広告計測のリンク、いずれの入口からでも「目的の一画面」までまっすぐ運べるようになります。

ここからは Rork Max(Expo ベース)で動くアプリに、URL Scheme と Universal Links の両方を実装する手順と、実機で初めて気づくつまずきどころを順に見ていきます。

URL Scheme と Universal Links、どちらをいつ使うか

二つの方式は役割が違います。URL Schememyapp://path)は実装が最も手軽で、アプリ内テストや他アプリからの呼び出しに向いています。ただしアプリが未インストールだとタップしても何も起きず、ユーザーには「壊れたリンク」に見えてしまいます。

Universal Links(iOS)と App Links(Android)は、どちらも https://myapp.com/path という通常の Web URL を使います。アプリが入っていれば直接アプリが開き、入っていなければそのまま Web ページが開く — この「切り替えのシームレスさ」が最大の利点で、Apple も公式に推奨している方式です。マーケティングや通知から外部に出す URL は、原則 Universal Links / App Links を使うのが安全だと考えています。

実務での使い分けはシンプルで、アプリ内部やデバッグでは URL Scheme、ユーザーに渡すリンクは Universal Links、と二段構えにしておくと迷いません。

app.config でスキームと関連ドメインを設定する

Rork Max が生成したプロジェクトは Expo 構成なので、app.json(または app.config.ts)に入口を宣言します。

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

scheme が URL Scheme(myapp://)の登録、associatedDomainsintentFilters が Universal Links / App Links の宣言です。ここまでは設定ファイルだけの話で、つまずく人はほとんどいません。問題は次のサーバー側にあります。

Universal Links が「効かない」ときに最初に疑う場所

「設定は全部入れたのに、リンクをタップすると Safari が開いてしまう」— Universal Links で最もよく聞く症状で、原因のほとんどはアプリ側ではなくサーバー側の検証ファイルにあります。

iOS は、宣言したドメインの https://myapp.com/.well-known/apple-app-site-association(AASA)というファイルを取りに行き、内容が一致して初めてアプリを開きます。このファイルでつまずく典型が三つあります。配信パスが /.well-known/ 配下になっていない、Content-Typeapplication/json でない、そして途中でリダイレクト(301/302)が挟まっている、の三つです。いずれか一つでも該当すると iOS は黙ってあきらめ、ただの Web リンクとして Safari を開きます。

正しい AASA は、リダイレクトなしの 200 で次の JSON を返す状態です。

{
  "applinks": {
    "details": [
      {
        "appIDs": ["YOUR_TEAM_ID.com.example.myapp"],
        "components": [
          { "/": "/wallpaper/*", "comment": "壁紙詳細へのリンク" }
        ]
      }
    ]
  }
}

Android 側は https://myapp.com/.well-known/assetlinks.json を見ます。

[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.myapp",
      "sha256_cert_fingerprints": ["YOUR_SHA256_FINGERPRINT"]
    }
  }
]

ここで私がはまったのは sha256_cert_fingerprints でした。手元のデバッグ証明書のフィンガープリントを入れたまま本番 AAB を出すと、Play 署名鍵と一致せず App Links が検証されません。本番では Play Console の「アプリの署名」に表示される SHA-256 を使う、というのが地味ですが大事なポイントです。AASA / assetlinks は配置したら、ブラウザで実際にその URL を開いて、リダイレクトなしの素の JSON が返ることを必ず目視確認するようにしています。

React Navigation との統合とコールド起動の取りこぼし

入口が通ったら、URL をどの画面に割り当てるかを定義します。Rork Max には自然言語でそのまま頼めます。

「アプリに次のディープリンクを追加してください:
- myapp://home → ホーム画面
- myapp://wallpaper/:id → 壁紙詳細画面
- myapp://profile → プロフィール画面
React Navigation の linking 設定を使って実装してください。」

生成される linking 設定はおおむね次の形になります。

const linking = {
  prefixes: ['myapp://', 'https://myapp.com'],
  config: {
    screens: {
      Home: 'home',
      WallpaperDetail: 'wallpaper/:id',
      Profile: 'profile',
    },
  },
};
 
<NavigationContainer linking={linking}>
  {/* ... */}
</NavigationContainer>

ここで実機でしか気づけない落とし穴があります。リンクの受け取りには二つの経路があり、アプリが起動中(ウォーム) のときは Linking.addEventListener('url', ...) が発火しますが、アプリが完全に終了した状態(コールド) から起動したときは、最初の URL を Linking.getInitialURL() で拾わないと取りこぼします。

2014年から個人開発で壁紙アプリを作り続けてきましたが、私自身がこれに遭遇したのもその一つでした。自前で addEventListener だけを書いていたため、通知から「アプリが落ちている状態」でタップすると、毎回ホーム画面に着地してしまう。期待した壁紙画面には飛ばないのです。

// Before: ウォーム起動しか拾えず、コールド起動を取りこぼす
import * as Linking from 'expo-linking';
 
useEffect(() => {
  const sub = Linking.addEventListener('url', ({ url }) => handleUrl(url));
  return () => sub.remove();
}, []);
// After: 起動時の初期 URL も明示的に拾う
import * as Linking from 'expo-linking';
 
useEffect(() => {
  // コールド起動: アプリを開いた URL を取得
  Linking.getInitialURL().then((url) => {
    if (url) handleUrl(url);
  });
  // ウォーム起動: 実行中の遷移
  const sub = Linking.addEventListener('url', ({ url }) => handleUrl(url));
  return () => sub.remove();
}, []);

幸い、React Navigation の linking 設定を使う場合は、内部で getInitialURL を読んでくれるため、この二経路は自動的に処理されます。逆に言えば、linking 設定に任せず自前でハンドリングするときだけ、コールド起動を自分で拾う必要がある、と覚えておくと混乱しません。

プッシュ通知から特定の画面へ運ぶ

最後に、冒頭の「通知を開いたらホームに戻される」を解消します。通知のデータ部にディープリンク URL を載せ、タップ時にそれを開くだけです。

// 送信側(サーバー): 通知データに遷移先 URL を含める
const notification = {
  title: "今日の壁紙が届きました",
  body: "新しい空のグラデーションを追加しました",
  data: { url: "myapp://wallpaper/sky-blue" }
};
 
// アプリ側: タップ時に URL を開く
import * as Notifications from 'expo-notifications';
 
Notifications.addNotificationResponseReceivedListener((response) => {
  const url = response.notification.request.content.data?.url;
  if (url) Linking.openURL(url);
});

Linking.openURL を呼べば、先ほど定義した linking 設定が URL を解釈して、そのまま壁紙詳細画面まで運んでくれます。通知の開封率そのものより、開いた後にどこへ着地するかのほうがリテンションに効きます。AdMob の広告収益を支えに個人開発を続けてきた立場としても、離脱を一つ減らすこの設計は、地味ながら効果が大きいと感じています。

次の一歩

まずは URL Scheme だけを設定し、シミュレータのターミナルから npx uri-scheme open "myapp://wallpaper/sky-blue" --ios を実行して、狙った画面に着地するかを確認してみてください。ここが通れば、あとは AASA / assetlinks を整えるだけで Universal Links まで地続きです。実装の参考になれば幸いです。

シェア

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

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

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

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

関連記事

開発ツール2026-05-24
Rork アプリでバックグラウンド復帰時に画面が真っ白/状態が消える原因の切り分け
Rork で作ったアプリをバックグラウンドに置いたまま数時間放置して戻ると、画面が真っ白になったり、編集途中のデータが消えていたり、再ログインを求められたりする。AppState・AsyncStorage 復元タイミング・iOS のメモリ回収・Navigation state 復元の4つの観点から切り分ける手順を、5,000万DL の壁紙・癒し系アプリを個人開発してきた経験を交えてまとめます。
開発ツール2026-05-23
Rork 生成プロジェクト特有の expo start --offline forbidden — テンプレ構成が原因の4パターン
Rork が生成するテンプレート構成が原因で expo start --offline が forbidden になる4つのパターン(tsconfigPaths・expo-router キャッシュ・ネイティブプリビルド・lock 不一致)を、Rork プロジェクト固有の観点で切り分けます。汎用的な Expo 側の対処(プロキシ403・依存検証)は別記事にまとめています。
開発ツール2026-05-20
iOS の 0x8badf00d ウォッチドッグ強制終了に Rork アプリが落ちるときの対処
Rork で作った iOS アプリが起動直後に消える。クラッシュログに 0x8badf00d。Apple のウォッチドッグが React Native の重い初期化を切ったときの正しい直し方を、私の運用中アプリで実際に効いた手順で説明します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →