◉RORK LABEN
●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…
記事一覧/開発ツール
⬡ 開発ツール/2026-05-18中級

Rork で Linking.canOpenURL が iOS だけ false を返す ─ LSApplicationQueriesSchemes 設定漏れの直し方

TikTok や X、LINE 公式アカウントへ飛ばす実装が iOS の本番ビルドだけで動かない症状を、LSApplicationQueriesSchemes の追加と EAS Build の再生成手順で確実に解消します。

Rork585React Native239iOS115LinkingInfo.plist6トラブルシューティング85

壁紙アプリの宣伝枠に「TikTok を開く」「X でシェアする」を追加した翌日、レビュアーから「iPhone で押しても何も起きない」という連絡が立て続けに 3 件届いたことがあります。シミュレーターでも開発用クライアントでも動いていたので、ストア配布ビルドだけで起きる症状の切り分けに半日かかりました。

原因は拍子抜けするほど単純で、Info.plist に LSApplicationQueriesSchemes を一行も書いていなかっただけでした。コードを疑い続けた半日は、いま思えば設定ファイルを開くまでの遠回りだったのだと思います。

「開けるか」を聞く前に、聞いてよい相手を宣言しておきます。 今回の線引きは、この一文に尽きます。

まず症状の特徴を確かめる

似たような不具合に見えても、LSApplicationQueriesSchemes 起因かどうかは挙動でほぼ判別できます。次の 4 つが揃っていれば、ほぼ確定です。

観察されること意味
シミュレーターと開発ビルドでは動き、TestFlight や本番ビルドだけ動かないビルドごとに `Info.plist` の中身が違う可能性
`canOpenURL("tiktok://...")` が `false` を返す、または reject されるスキームが未宣言
Xcode のコンソールに `This app is not allowed to query for scheme tiktok` と出る未宣言の決定的な証拠
Android の同じコードは動いているiOS 固有の制限(ただし Android 11 以降にも別の制限があります。後述)

React Native の公式ドキュメントにも、iOS 9 以降は LSApplicationQueriesSchemes を Info.plist に書かないと canOpenURL() が常に false に解決される、と明記されています。Apple がプライバシー保護として導入した制限で、他アプリのインストール状況を探る行為を防ぐためのものです。

openURL 自体は宣言なしでも実行できる場合があります。ところが「インストールされていなければウェブ版を開く」という分岐を canOpenURL で書いていると、常に false になって「未インストール」と誤判定されます。ユーザーは TikTok を入れているのに、毎回 Safari のウェブ版へ飛ばされる——そういう体験になります。

修正 1: app.json に ios.infoPlist.LSApplicationQueriesSchemes を追加する

Rork は Expo の app config を踏襲しているので、Info.plist を直接いじる必要はありません。プロジェクトルートの app.json(または app.config.ts)に次のキーを足します。

{
  "expo": {
    "name": "MyApp",
    "ios": {
      "bundleIdentifier": "com.example.myapp",
      "infoPlist": {
        "LSApplicationQueriesSchemes": [
          "tiktok",
          "twitter",
          "instagram",
          "fb",
          "fb-messenger",
          "youtube",
          "comgooglemaps",
          "spotify",
          "slack"
        ]
      }
    }
  }
}

app.config.ts を使っている場合は次のようになります。

import type { ExpoConfig } from "expo/config";
 
const config: ExpoConfig = {
  name: "MyApp",
  slug: "myapp",
  ios: {
    bundleIdentifier: "com.example.myapp",
    infoPlist: {
      LSApplicationQueriesSchemes: [
        "tiktok",
        "twitter",
        "instagram",
        "fb",
        "fb-messenger",
        "youtube",
        "comgooglemaps",
        "spotify",
        "slack",
      ],
    },
  },
};
 
export default config;

http / https や mailto・tel・sms のような標準スキームは、私の手元では宣言なしでも開けていました。以前は念のため並べていましたが、いまは「宣言が必要なものだけを書く」ほうが、あとから棚卸しするときに迷わないと感じています。

修正 2: 実際の URL Scheme を間違えない

ハマりやすいのが「アプリ名と Scheme 名が一致しない」ケースです。X を開きたいときに "x" と書いても期待どおりには動かず、私が確認した範囲では "twitter" を使っていました。ただしアプリ側の都合で将来変わる可能性があるため、出荷前に実機で canOpenURL を呼んで確かめる習慣をおすすめします。LINE の友だち追加は line:// より、Universal Link(https://line.me/R/ti/p/...)のほうが安全です。

代表的なスキームを表にまとめます。

開きたいものスキーム宣言
TikTok`tiktok`必要
X(旧 Twitter)`twitter`必要
Instagram`instagram`必要
Facebook / Messenger`fb` / `fb-messenger`必要
YouTube`youtube`(または `vnd.youtube`)必要
Google マップ`comgooglemaps`必要
Spotify / Slack`spotify` / `slack`必要
LINE 友だち追加Universal Link(`https`)不要(`https` のため)

これ以外のアプリは、各社の公式ドキュメントで「URL Scheme」を探すのがいちばん確実です。第三者がまとめた一覧はバージョンで古くなるため、引用する前に実機で確認しています。

修正 3: EAS Build を再ビルドしないと反映されない

ここでつまずく方がとても多いので、強調しておきます。app.json を編集しただけでは、開発中の Expo Go や既存の Development Build には反映されません。infoPlist は ネイティブビルドに焼き込まれる設定 なので、次のいずれかで作り直す必要があります。

# 開発用クライアントを作り直す
eas build --profile development --platform ios
 
# 本番ビルドを作り直す
eas build --profile production --platform ios
 
# ローカルで prebuild して実機へ
npx expo prebuild --clean
npx expo run:ios --device

expo prebuild --clean は ios/ ディレクトリを再生成します。独自に追加したファイルがある場合は、事前に差分を退避しておいてください。

修正 4: canOpenURL に頼らない書き方も持っておく

設定が入っても、コード側に「失敗時にウェブ版へ飛ばす」処理がなければ、スキームが変わった瞬間にまた同じ症状が出ます。React Native の公式ドキュメントによれば、canOpenURL は iOS でスキーム未宣言のとき reject されることがあり、openURL は開けるアプリがないとき reject されます。つまり どちらの失敗も try/catch で受けて、最後はウェブへ という形にしておけば、設定漏れがあってもユーザーは行き止まりになりません。

// utils/openExternalApp.ts
import { Linking } from "react-native";
 
type AppLink = {
  /** ネイティブアプリを開く URL(Scheme または Universal Link) */
  appUrl: string;
  /** 開けなかった場合のウェブフォールバック */
  webUrl: string;
};
 
export async function openExternalApp({ appUrl, webUrl }: AppLink): Promise<void> {
  try {
    if (await Linking.canOpenURL(appUrl)) {
      await Linking.openURL(appUrl);
      return;
    }
  } catch (e) {
    // 未宣言スキームでの reject、または openURL の reject を受ける
    if (__DEV__) {
      console.warn("[openExternalApp] fallback to web", e);
    }
  }
  await Linking.openURL(webUrl);
}
 
// 使い方
// await openExternalApp({
//   appUrl: "tiktok://user?username=example",
//   webUrl: "https://www.tiktok.com/@example",
// });

try/catch を外側に置く点が肝です。ただし、このフォールバックは設定漏れを隠す働きもします。開発ビルドでは警告を必ず出し、本番で静かにウェブへ流れ続けることのないよう、確認の機会を残しておくと安心です。

修正 5: 回数の上限と、Android 側の別ルール

React Native の公式ドキュメントには、古い iOS 向けにリンクされたアプリが iOS 9 以降で動く場合、canOpenURL を呼べるのは 50 回まで で、超えると以降は常に false になり、再インストールやアップグレードでリセットされる、という記述があります。宣言できるスキームの件数の上限については、今回 Apple の公式ページを確認できなかったため、断定は避けます。ただ、ボタンを表示するたびに canOpenURL を呼んでいる画面は、回数の面でも無駄が出ます。起動時に一度だけ調べて結果を保持するほうが、私は扱いやすいと感じています。

もう一つ、Android 11(SDK 30)以降 を対象にすると、AndroidManifest.xml に <queries> でインテントを書かない限り、同じ種類の問い合わせが失敗することが公式ドキュメントに記されています。「Android は動いている」という観察は、対象 SDK によっては成り立ちません。

それでも canOpenURL が false の時に疑う 3 つのこと

ここまでやってもなお false が返るときは、次の 3 点を順に確認します。

  1. ビルドが古い: ビルド番号と、TestFlight で配信されているものが一致しているか。私は app.json を直したのに古いビルドを再提出してしまい、半日を無駄にしたことがあります。
  2. EAS のキャッシュ: eas build に --clear-cache を付けて再実行します。EAS はクラウド上でビルドするため、手元の Xcode の DerivedData を消しても効きません。
  3. スキームの綴り: 大文字小文字や :// の有無を含め、宣言した文字列と呼び出す URL が一致しているかを見直します。

次にやること

今日のうちに、お使いの Rork プロジェクトの app.json を開き、外部アプリへ飛ばしているスキームが LSApplicationQueriesSchemes に載っているかを一つずつ照らし合わせていただければと思います。載っていないものが一つでもあれば、iOS のユーザーは気づかないまま、毎回ウェブへ流れているかもしれません。

シェア

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

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

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

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

関連記事

⬡ 開発ツール2026-09-21
exit code 0 で止まるビルド — ログを黙らせていた設定と、空ファイルを通した存在チェック
eas build --local が exit code 0 but produced no further output で止まる件と、0 バイトのスタブが埋め込まれず dyld で落ちる件。どちらも原因は出力を減らす設定でした。自分のスクリプトを棚卸しする手順まで書き残します。
⬡ 開発ツール2026-07-27
最低サポート iOS をいつ上げるか — 利用率ではなく、コードに残った分岐の数で決める
最低サポート OS の引き上げを利用率で判断すると、毎年同じ結論になって先送りが続きます。OS 分岐に注記を付けて棚卸しし、iOS と Android の引き上げ先ごとに消せる分岐を数えるスクリプトと、引き上げ後に見る指標をまとめました。
⬡ 開発ツール2026-05-28
Rork アプリで iOS の BGTaskScheduler.submit が Error Code=1 で失敗するときの切り分け手順
BGTaskScheduler.submit が Error Code=1 を返すとき、原因は 6 種類に収まります。Code=1 と Code=3 の読み分け、Expo 構成に固有の identifier、ウィジェット拡張から submit してしまう落とし穴、getPendingTaskRequests による自己確認まで整理しました。