Capacitor Deep Linking

cap-go/capgo-skills/skills/capacitor-deep-linking

作者 cap-go2cfb277a1488无许可证72 个星标收录于 2026年10月8日更新于 2026年10月8日仓库今天更新

Complete guide to implementing deep links and universal links in Capacitor apps. Covers iOS Universal Links, Android App Links, custom URL schemes, and navigation handling. Use this skill when users need to open their app from links.

AI 生成的概览

在 Capacitor 移动应用中实现深度链接、通用链接和应用链接的指南。

功能
该技能提供为 Capacitor 应用添加深度链接的说明,涵盖自定义 URL scheme、iOS 通用链接和 Android 应用链接。它讲解如何配置 Info.plist、AndroidManifest.xml、apple-app-site-association 和 assetlinks.json 文件,以及如何使用 Capacitor App 插件处理传入的 URL。还包含 React Router 与 Vue Router 的路由集成示例、OAuth 回调处理、查询参数解析、延迟深度链接、测试命令和常见问题排查。
适用场景
当用户希望通过链接打开其 Capacitor 应用、设置通用链接或应用链接、定义自定义 URL scheme,或处理分享与邀请链接时使用。在调试链接在浏览器中打开而非应用时也适用。
运行要求
需要一个已安装 @capacitor/app 插件的 Capacitor 移动项目,以及 iOS 和 Android 平台配置文件。通用链接和应用链接需要在应用域名上通过 HTTPS 托管关联文件。该技能不包含脚本,仅为说明和代码示例。

Deep Linking in Capacitor

Implement deep links, universal links, and app links in Capacitor apps.

When to Use This Skill

  • User wants deep links
  • User needs universal links
  • User asks about URL schemes
  • User wants to open app from links
  • User needs share links

Types of Deep Links

TypePlatformFormatRequires Server
Custom URL SchemeBothmyapp://pathNo
Universal LinksiOShttps://myapp.com/pathYes
App LinksAndroidhttps://myapp.com/pathYes

Quick Start

Install Plugin

bash
npm install @capacitor/appnpx cap sync

Handle Deep Links

typescript
import { App } from '@capacitor/app';
// Listen for deep link opensApp.addListener('appUrlOpen', (event) => {  console.log('App opened with URL:', event.url);
  // Parse and navigate  const url = new URL(event.url);  handleDeepLink(url);});
function handleDeepLink(url: URL) {  // Custom scheme: myapp://product/123  // Universal link: https://myapp.com/product/123
  const path = url.pathname || url.host + url.pathname;
  // Route based on path  if (path.startsWith('/product/')) {    const productId = path.split('/')[2];    navigateTo(`/product/${productId}`);  } else if (path.startsWith('/user/')) {    const userId = path.split('/')[2];    navigateTo(`/profile/${userId}`);  } else if (path === '/login') {    navigateTo('/login');  } else {    navigateTo('/');  }}

Custom URL Scheme

iOS Configuration

xml
<!-- ios/App/App/Info.plist --><key>CFBundleURLTypes</key><array>    <dict>        <key>CFBundleURLName</key>        <string>com.yourcompany.yourapp</string>        <key>CFBundleURLSchemes</key>        <array>            <string>myapp</string>            <string>myapp-dev</string>        </array>    </dict></array>

Android Configuration

xml
<!-- android/app/src/main/AndroidManifest.xml --><activity android:name=".MainActivity">    <!-- Deep link intent filter -->    <intent-filter>        <action android:name="android.intent.action.VIEW" />        <category android:name="android.intent.category.DEFAULT" />        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="myapp" />    </intent-filter></activity>

Test Custom Scheme

bash
# iOS Simulatorxcrun simctl openurl booted "myapp://product/123"
# Androidadb shell am start -a android.intent.action.VIEW -d "myapp://product/123"

Universal Links (iOS)

1. Enable Associated Domains

In Xcode:

  1. Select App target
  2. Signing & Capabilities
    • Capability > Associated Domains
  3. Add: applinks:myapp.com

2. Create apple-app-site-association

Host at https://myapp.com/.well-known/apple-app-site-association:

json
{  "applinks": {    "apps": [],    "details": [      {        "appID": "TEAMID.com.yourcompany.yourapp",        "paths": [          "/product/*",          "/user/*",          "/invite/*",          "NOT /api/*"        ]      }    ]  }}

Requirements:

  • Served over HTTPS
  • Content-Type: application/json
  • No redirects
  • File at root domain

3. Info.plist

xml
<!-- ios/App/App/Info.plist --><key>com.apple.developer.associated-domains</key><array>    <string>applinks:myapp.com</string>    <string>applinks:www.myapp.com</string></array>

Verify Universal Links

bash
# Validate AASA filecurl -I https://myapp.com/.well-known/apple-app-site-association
# Check Apple CDN cachecurl "https://app-site-association.cdn-apple.com/a/v1/myapp.com"

App Links (Android)

1. Create assetlinks.json

Host at https://myapp.com/.well-known/assetlinks.json:

json
[  {    "relation": ["delegate_permission/common.handle_all_urls"],    "target": {      "namespace": "android_app",      "package_name": "com.yourcompany.yourapp",      "sha256_cert_fingerprints": [        "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"      ]    }  }]

Get SHA256 Fingerprint

bash
# Debug keystorekeytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
# Release keystorekeytool -list -v -keystore release.keystore -alias your-alias
# From APKkeytool -printcert -jarfile app-release.apk

2. AndroidManifest.xml

xml
<!-- android/app/src/main/AndroidManifest.xml --><activity android:name=".MainActivity">    <!-- App Links intent filter -->    <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" />        <data android:host="myapp.com" />        <data android:pathPrefix="/product" />        <data android:pathPrefix="/user" />        <data android:pathPrefix="/invite" />    </intent-filter></activity>

Verify App Links

bash
# Validate assetlinks.jsoncurl https://myapp.com/.well-known/assetlinks.json
# Use Google's validatorhttps://developers.google.com/digital-asset-links/tools/generator
# Check link handling on deviceadb shell pm get-app-links com.yourcompany.yourapp

Advanced Routing

React Router Integration

typescript
import { App } from '@capacitor/app';import { useHistory } from 'react-router-dom';import { useEffect } from 'react';
function DeepLinkHandler() {  const history = useHistory();
  useEffect(() => {    App.addListener('appUrlOpen', (event) => {      const url = new URL(event.url);      const path = getPathFromUrl(url);
      // Navigate using React Router      history.push(path);    });
    // Check if app was opened with URL    App.getLaunchUrl().then((result) => {      if (result?.url) {        const url = new URL(result.url);        const path = getPathFromUrl(url);        history.push(path);      }    });  }, []);
  return null;}
function getPathFromUrl(url: URL): string {  // Handle both custom scheme and https  if (url.protocol === 'myapp:') {    return '/' + url.host + url.pathname;  }  return url.pathname + url.search;}

Vue Router Integration

typescript
import { App } from '@capacitor/app';import { useRouter } from 'vue-router';import { onMounted } from 'vue';
export function useDeepLinks() {  const router = useRouter();
  onMounted(async () => {    App.addListener('appUrlOpen', (event) => {      const path = parseDeepLink(event.url);      router.push(path);    });
    const launchUrl = await App.getLaunchUrl();    if (launchUrl?.url) {      const path = parseDeepLink(launchUrl.url);      router.push(path);    }  });}

Deferred Deep Links

Handle links when app wasn't installed:

typescript
import { App } from '@capacitor/app';import { Preferences } from '@capacitor/preferences';
// On first launch, check for deferred linkasync function checkDeferredDeepLink() {  const { value: isFirstLaunch } = await Preferences.get({ key: 'firstLaunch' });
  if (isFirstLaunch !== 'false') {    await Preferences.set({ key: 'firstLaunch', value: 'false' });
    // Check with your attribution service    const deferredLink = await fetchDeferredLink();    if (deferredLink) {      handleDeepLink(new URL(deferredLink));    }  }}

Query Parameters

typescript
App.addListener('appUrlOpen', (event) => {  const url = new URL(event.url);
  // Get query parameters  const source = url.searchParams.get('source');  const campaign = url.searchParams.get('campaign');  const referrer = url.searchParams.get('ref');
  // Track attribution  analytics.logEvent('deep_link_open', {    path: url.pathname,    source,    campaign,    referrer,  });
  // Navigate with state  navigateTo(url.pathname, {    state: { source, campaign, referrer },  });});

OAuth Callback Handling

typescript
// Handle OAuth redirectApp.addListener('appUrlOpen', async (event) => {  const url = new URL(event.url);
  if (url.pathname === '/oauth/callback') {    const code = url.searchParams.get('code');    const state = url.searchParams.get('state');    const error = url.searchParams.get('error');
    if (error) {      handleOAuthError(error);      return;    }
    if (code && validateState(state)) {      await exchangeCodeForToken(code);      navigateTo('/home');    }  }});

Testing

Test Matrix

ScenarioCommand
Custom schememyapp://path
Universal link cold startTap link with app closed
Universal link warm startTap link with app in background
Universal link in SafariType URL in Safari
App link cold startTap link with app closed
App link in ChromeTap link in Chrome

Debug Tools

bash
# iOS: Check associated domains entitlementcodesign -d --entitlements - App.app | grep associated-domains
# iOS: Reset Universal Links cachexcrun simctl erase all
# Android: Check verified linksadb shell dumpsys package d | grep -A5 "Package: com.yourcompany.yourapp"

Common Issues

IssueSolution
Universal Links not workingCheck AASA file, SSL, entitlements
App Links not verifiedCheck assetlinks.json, fingerprint
Links open in browserCheck intent-filter, autoVerify
Cold start not handledUse App.getLaunchUrl()
Simulator issuesReset simulator, rebuild app

Resources

来源与署名

来源:cap-go/capgo-skills位于skills/capacitor-deep-linking提交2cfb277

许可证: 无许可证

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架