ANTIGRAVITY LABEN
記事一覧/アプリ開発
アプリ開発/2026-07-03上級

ユニバーサルリンクが黙って壊れる — 関連付けファイルのドリフトをエージェントの検証ゲートで塞ぐ

ユニバーサルリンク/App Linksは、関連付けファイルやエンタイトルメントがズレると例外を出さずにブラウザへ落ちます。ルート定義を単一の真実源にまとめ、エージェントに週次と公開前の検証を任せる設計をまとめました。

Universal LinksApp LinksAntigravity306iOS26Android25ディープリンク

プレミアム記事

私が個人で運営している壁紙アプリで、共有リンクを踏いても一度もアプリが開かず、静かに Safari が立ち上がるだけになっていた時期があります。クラッシュもエラーログも出ません。ある日ユーザーからの問い合わせで気づき、原因を追うのに半日を溶かしました。

ユニバーサルリンク(iOS)と App Links(Android)は、うまく動いているときはただの URL に見えます。しかし裏側では、アプリのエンタイトルメント、OS が検証のために取りに行く関連付けファイル、そしてアプリ内のインテントフィルタという三つの独立した宣言が、寸分の狂いもなく一致していることが前提になっています。どれか一つがズレた瞬間、リンクは例外ではなく「ブラウザで開く」という一見正常な挙動に落ちます。これが厄介です。壊れても誰も気づかないのです。

ここでは、この沈黙する破綻を、宣言を人手で二重管理することをやめ、Antigravity のエージェントに週次と公開前の突き合わせを任せる設計として整理します。個人開発でリンクの保守に何度も時間を取られてきた私自身の経験から、実際に効いた形をまとめます。

なぜ黙って壊れるのか — 三つの宣言が別々に管理される

リンクが開くまでには、次の三者がそれぞれ別の場所で宣言されています。ズレやすいのは、これらが別々のリポジトリ・別々の担当・別々の変更タイミングで動くからです。

宣言置き場所ズレる典型例
関連付けファイル(AASA / assetlinks.json)Webサーバーの /.well-known/ドメイン移設・CDN のキャッシュ・Content-Type 誤り
アプリの宣言(Associated Domains / intent-filter)エンタイトルメント・AndroidManifestドメイン追加漏れ・autoVerify 抜け・パス変更
署名情報(Androidのフィンガープリント)assetlinks.json 内の SHA-256アップロード鍵の入れ替え・Play アプリ署名の証明書変更

とくにエージェントに UI やルーティングの改修を任せていると、新しい画面に対応する新しいパスがアプリ側に増えても、公開ファイルの更新が置き去りになりがちです。生成されたコードは正しく動いているのに、リンクだけが静かに外れる。この非対称が、放っておくと積もっていきます。

ルート定義を単一の真実源にする

対策の起点は、リンクのパス設計を三箇所に手書きするのをやめ、1 枚の定義ファイルに集約することです。ここから関連付けファイルを生成すれば、少なくともアプリと公開ファイルの食い違いは構造的に起きなくなります。

// link-routes.json — リンク設計の唯一の真実源
{
  "domain": "example.com",
  "ios": {
    "appID": "TEAMID123.com.example.wallpaper"
  },
  "android": {
    "package": "com.example.wallpaper",
    "sha256": ["AA:BB:CC:...:99"]
  },
  "routes": [
    { "id": "collection", "path": "/c/*", "screen": "CollectionScreen" },
    { "id": "wallpaper",   "path": "/w/*", "screen": "WallpaperScreen" },
    { "id": "invite",      "path": "/invite/*", "screen": "InviteScreen" }
  ]
}

この 1 枚から、iOS の AASA と Android の assetlinks.json をコードで書き出します。手で JSON を編集させないことが肝心です。

// gen-association-files.mjs — 依存ゼロ。ルート定義から公開ファイルを生成
import { readFileSync, mkdirSync, writeFileSync } from "node:fs";
 
const cfg = JSON.parse(readFileSync("link-routes.json", "utf8"));
 
// iOS: apple-app-site-association
const aasa = {
  applinks: {
    apps: [],
    details: [
      {
        appID: cfg.ios.appID,
        paths: cfg.routes.map((r) => r.path),
      },
    ],
  },
};
 
// Android: Digital Asset Links
const assetlinks = [
  {
    relation: ["delegate_permission/common.handle_all_urls"],
    target: {
      namespace: "android_app",
      package_name: cfg.android.package,
      sha256_cert_fingerprints: cfg.android.sha256,
    },
  },
];
 
mkdirSync("public/.well-known", { recursive: true });
// 拡張子なし・Content-Type は application/json で配信すること
writeFileSync("public/.well-known/apple-app-site-association", JSON.stringify(aasa, null, 2));
writeFileSync("public/.well-known/assetlinks.json", JSON.stringify(assetlinks, null, 2));
 
console.log(`generated ${cfg.routes.length} routes for ${cfg.domain}`);

アプリ側の宣言も、同じ定義から確認できる状態にしておきます。iOS のエンタイトルメントと、Android のインテントフィルタは次の形です。

<!-- AndroidManifest.xml — autoVerify を必ず付ける -->
<intent-filter android:autoVerify="true">
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data android:scheme="https" android:host="example.com" />
  <data android:pathPrefix="/c/" />
  <data android:pathPrefix="/w/" />
  <data android:pathPrefix="/invite/" />
</intent-filter>

iOS 側は Associated Domainsapplinks:example.com を宣言します。ここでドメインの綴りやサブドメインの有無が一文字でも違えば、OS は関連付けファイルを取りに行かず、リンクは静かにブラウザへ落ちます。 ここでの注意点は、綴りの一文字違いがエラーとして表面化せず、アプリ側の対処だけでは気づけないことです。

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

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
AASAとassetlinks.jsonを手書きせず、ルート定義1枚から生成して食い違いを構造的に断つ手順
エンタイトルメント/インテントフィルタ/公開ファイルの三者を突き合わせる、依存ゼロの検証スクリプト
公開前と週次無人ランでドリフトを捕捉する二段ゲートと、6週間運用した実測値
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

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

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

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

関連記事

アプリ開発2026-05-25
Antigravity の Inline Edit と Agent モードを壁紙アプリ運用で1ヶ月使い分けた所感
壁紙アプリ4本の実運用で Antigravity の Inline Edit と Agent モードを1ヶ月使い分けた所感です。実測値・AGENTS.md への判断軸の固定・dSYM 一括統一・クレジット消費まで、現実的な運用設計を共有します。
アプリ開発2026-04-18
AntigravityでアートアプリをiOS/Androidに同時リリースした話 — 企画・開発・審査の全記録
アーティストである自分のポートフォリオアプリをAntigravityで作り、iOS/Android同時リリースした実体験。技術だけでなくコンセプト設計から審査通過まで全部記録しました。
アプリ開発2026-04-02
AntigravityでARアプリを作る入門ガイド 2026 — ARKit・ARCoreで拡張現実を体験しよう
AntigravityのAIエージェントを活用してARKit(iOS)・ARCore(Android)の拡張現実アプリを効率よく開発する入門ガイド。2026年のAR市場動向からステップバイステップの実装手順まで丁寧に解説します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →