capacitor-deep-linking

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Deep Linking in Capacitor

Capacitor中的深度链接

Implement deep links, universal links, and app links in Capacitor apps.
在Capacitor应用中实现深度链接、通用链接和App Links。

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
  • 用户需要深度链接
  • 用户需要通用链接
  • 用户询问URL方案相关问题
  • 用户希望通过链接打开应用
  • 用户需要分享链接

Types of Deep Links

深度链接的类型

TypePlatformFormatRequires Server
Custom URL SchemeBoth
myapp://path
No
Universal LinksiOS
https://myapp.com/path
Yes
App LinksAndroid
https://myapp.com/path
Yes
类型平台格式是否需要服务器
自定义URL方案双平台
myapp://path
Universal LinksiOS
https://myapp.com/path
App LinksAndroid
https://myapp.com/path

Quick Start

快速开始

Install Plugin

安装插件

bash
bun add @capacitor/app
bunx cap sync
bash
bun add @capacitor/app
bunx cap sync

Handle Deep Links

处理深度链接

typescript
import { App } from '@capacitor/app';

// Listen for deep link opens
App.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('/');
  }
}
typescript
import { App } from '@capacitor/app';

// 监听深度链接打开事件
App.addListener('appUrlOpen', (event) => {
  console.log('应用通过以下URL打开:', event.url);

  // 解析并导航
  const url = new URL(event.url);
  handleDeepLink(url);
});

function handleDeepLink(url: URL) {
  // 自定义方案: myapp://product/123
  // 通用链接: https://myapp.com/product/123

  const path = url.pathname || url.host + url.pathname;

  // 根据路径路由
  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

自定义URL方案

iOS Configuration

iOS配置

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>
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

Android配置

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>
xml
<!-- android/app/src/main/AndroidManifest.xml -->
<activity android:name=".MainActivity">
    <!-- 深度链接意图过滤器 -->
    <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
undefined
bash
undefined

iOS Simulator

iOS模拟器

xcrun simctl openurl booted "myapp://product/123"
xcrun simctl openurl booted "myapp://product/123"

Android

Android

adb shell am start -a android.intent.action.VIEW -d "myapp://product/123"
undefined
adb shell am start -a android.intent.action.VIEW -d "myapp://product/123"
undefined

Universal Links (iOS)

Universal Links(iOS)

1. Enable Associated Domains

1. 启用关联域名

In Xcode:
  1. Select App target
  2. Signing & Capabilities
    • Capability > Associated Domains
  3. Add:
    applinks:myapp.com
在Xcode中:
  1. 选择应用目标
  2. 进入Signing & Capabilities
    • Capability > Associated Domains
  3. 添加:
    applinks:myapp.com

2. Create apple-app-site-association

2. 创建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
托管在
https://myapp.com/.well-known/apple-app-site-association
json
{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "TEAMID.com.yourcompany.yourapp",
        "paths": [
          "/product/*",
          "/user/*",
          "/invite/*",
          "NOT /api/*"
        ]
      }
    ]
  }
}
要求:
  • 通过HTTPS提供服务
  • Content-Type:
    application/json
  • 无重定向
  • 文件位于根域名下

3. Info.plist

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>
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

验证Universal Links

bash
undefined
bash
undefined

Validate AASA file

验证AASA文件

Check Apple CDN cache

检查Apple CDN缓存

App Links (Android)

App Links(Android)

1. Create assetlinks.json

1. 创建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"
      ]
    }
  }
]
托管在
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

获取SHA256指纹

bash
undefined
bash
undefined

Debug keystore

调试密钥库

keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

Release keystore

发布密钥库

keytool -list -v -keystore release.keystore -alias your-alias
keytool -list -v -keystore release.keystore -alias your-alias

From APK

从APK获取

keytool -printcert -jarfile app-release.apk
undefined
keytool -printcert -jarfile app-release.apk
undefined

2. AndroidManifest.xml

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>
xml
<!-- android/app/src/main/AndroidManifest.xml -->
<activity android:name=".MainActivity">
    <!-- App Links意图过滤器 -->
    <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

验证App Links

bash
undefined
bash
undefined

Validate assetlinks.json

验证assetlinks.json

Use Google's validator

使用Google验证工具

Check link handling on device

在设备上检查链接处理情况

adb shell pm get-app-links com.yourcompany.yourapp
undefined
adb shell pm get-app-links com.yourcompany.yourapp
undefined

Advanced Routing

高级路由

React Router Integration

React Router集成

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;
}
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);

      // 使用React Router导航
      history.push(path);
    });

    // 检查应用是否通过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 {
  // 处理自定义方案和https链接
  if (url.protocol === 'myapp:') {
    return '/' + url.host + url.pathname;
  }
  return url.pathname + url.search;
}

Vue Router Integration

Vue Router集成

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);
    }
  });
}
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 link
async 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));
    }
  }
}
处理应用未安装时的链接:
typescript
import { App } from '@capacitor/app';
import { Preferences } from '@capacitor/preferences';

// 首次启动时,检查延迟链接
async function checkDeferredDeepLink() {
  const { value: isFirstLaunch } = await Preferences.get({ key: 'firstLaunch' });

  if (isFirstLaunch !== 'false') {
    await Preferences.set({ key: 'firstLaunch', value: 'false' });

    // 调用归因服务检查
    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 },
  });
});
typescript
App.addListener('appUrlOpen', (event) => {
  const url = new URL(event.url);

  // 获取查询参数
  const source = url.searchParams.get('source');
  const campaign = url.searchParams.get('campaign');
  const referrer = url.searchParams.get('ref');

  // 跟踪归因
  analytics.logEvent('deep_link_open', {
    path: url.pathname,
    source,
    campaign,
    referrer,
  });

  // 携带状态导航
  navigateTo(url.pathname, {
    state: { source, campaign, referrer },
  });
});

OAuth Callback Handling

OAuth回调处理

typescript
// Handle OAuth redirect
App.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');
    }
  }
});
typescript
// 处理OAuth重定向
App.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 scheme
myapp://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
场景操作/命令
自定义方案
myapp://path
Universal Links冷启动应用关闭时点击链接
Universal Links热启动应用在后台时点击链接
Safari中的Universal Links在Safari中输入URL
App Links冷启动应用关闭时点击链接
Chrome中的App Links在Chrome中点击链接

Debug Tools

调试工具

bash
undefined
bash
undefined

iOS: Check associated domains entitlement

iOS: 检查关联域名权限

codesign -d --entitlements - App.app | grep associated-domains
codesign -d --entitlements - App.app | grep associated-domains

iOS: Reset Universal Links cache

iOS: 重置Universal Links缓存

xcrun simctl erase all
xcrun simctl erase all

Android: Check verified links

Android: 检查已验证链接

adb shell dumpsys package d | grep -A5 "Package: com.yourcompany.yourapp"
undefined
adb shell dumpsys package d | grep -A5 "Package: com.yourcompany.yourapp"
undefined

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
问题解决方案
Universal Links无法工作检查AASA文件、SSL证书、权限配置
App Links未验证检查assetlinks.json、指纹配置
链接在浏览器中打开检查意图过滤器、autoVerify配置
冷启动未处理使用
App.getLaunchUrl()
模拟器问题重置模拟器、重新构建应用

Resources

资源