universal-links

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
Use
bunx setup-safari
to configure and test universal links (deep links) on iOS.
使用
bunx setup-safari
在iOS上配置并测试通用链接(深度链接)。

Important: When Custom Builds Are Needed

重要提示:何时需要自定义构建

Universal links with custom domains require custom native builds. Your app will no longer work in Expo Go.
However, for basic deep linking during development:
  • Expo Go supports the
    exp://
    URL scheme and Expo-hosted URLs
  • Custom builds required for your own domain's universal links (applinks:yourdomain.com)
If you only need deep linking for development/testing, try
npx expo start
with Expo Go first. Only create custom builds when you need production universal links with your own domain.
使用自定义域名的通用链接需要自定义原生构建。 你的应用将无法再在Expo Go中运行。
不过,在开发期间进行基础深度链接时:
  • Expo Go支持
    exp://
    URL协议和Expo托管的URL
  • 需要自定义构建才能使用你自己域名的通用链接(applinks:yourdomain.com)
如果你仅在开发/测试时需要深度链接,建议先使用
npx expo start
搭配Expo Go。只有当你需要使用自有域名的生产环境通用链接时,再创建自定义构建。

Automated Setup (Recommended)

自动设置(推荐)

Run setup-safari with your Apple ID to automate credential lookup:
bash
EXPO_APPLE_ID="your-apple-id@email.com" bunx setup-safari
This will:
  1. Authenticate with Apple Developer Portal
  2. Enable Associated Domains for your bundle ID
  3. Output the AASA file content and meta tag
After running, you must manually:
使用你的Apple ID运行setup-safari以自动查找凭据:
bash
EXPO_APPLE_ID="your-apple-id@email.com" bunx setup-safari
这将完成以下操作:
  1. 与Apple开发者门户进行身份验证
  2. 为你的bundle ID启用关联域名
  3. 输出AASA文件内容和元标签
运行完成后,你必须手动执行以下步骤:

1. Create the AASA file

1. 创建AASA文件

Create
public/.well-known/apple-app-site-association
(no file extension):
json
{
  "applinks": {
    "details": [
      {
        "appIDs": ["TEAM_ID.com.your.bundleid"],
        "components": [
          {
            "/": "*",
            "comment": "Matches all routes"
          }
        ]
      }
    ]
  },
  "activitycontinuation": {
    "apps": ["TEAM_ID.com.your.bundleid"]
  },
  "webcredentials": {
    "apps": ["TEAM_ID.com.your.bundleid"]
  }
}
创建
public/.well-known/apple-app-site-association
(无文件扩展名):
json
{
  "applinks": {
    "details": [
      {
        "appIDs": ["TEAM_ID.com.your.bundleid"],
        "components": [
          {
            "/": "*",
            "comment": "Matches all routes"
          }
        ]
      }
    ]
  },
  "activitycontinuation": {
    "apps": ["TEAM_ID.com.your.bundleid"]
  },
  "webcredentials": {
    "apps": ["TEAM_ID.com.your.bundleid"]
  }
}

2. Create src/app/+html.tsx with Smart App Banner

2. 创建带有智能应用横幅的src/app/+html.tsx

tsx
import { ScrollViewStyleReset } from "expo-router/html";

export default function Root({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
        <meta
          name="viewport"
          content="width=device-width, initial-scale=1, shrink-to-fit=no"
        />
        <meta name="apple-itunes-app" content="app-id=YOUR_ITUNES_ID" />
        <ScrollViewStyleReset />
      </head>
      <body>{children}</body>
    </html>
  );
}
tsx
import { ScrollViewStyleReset } from "expo-router/html";

export default function Root({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
        <meta
          name="viewport"
          content="width=device-width, initial-scale=1, shrink-to-fit=no"
        />
        <meta name="apple-itunes-app" content="app-id=YOUR_ITUNES_ID" />
        <ScrollViewStyleReset />
      </head>
      <body>{children}</body>
    </html>
  );
}

3. Add Associated Domains to app.json

3. 在app.json中添加关联域名

json
{
  "expo": {
    "ios": {
      "associatedDomains": [
        "applinks:yourdomain.com",
        "activitycontinuation:yourdomain.com",
        "webcredentials:yourdomain.com"
      ]
    }
  }
}
json
{
  "expo": {
    "ios": {
      "associatedDomains": [
        "applinks:yourdomain.com",
        "activitycontinuation:yourdomain.com",
        "webcredentials:yourdomain.com"
      ]
    }
  }
}

4. Deploy and Rebuild

4. 部署并重新构建

bash
undefined
bash
undefined

Deploy web (AASA file must be accessible)

部署网页(AASA文件必须可访问)

npx expo export -p web && npx eas-cli deploy
npx expo export -p web && npx eas-cli deploy

Rebuild iOS app with new entitlements

使用新的权限重新构建iOS应用

npx expo run:ios
npx expo run:ios

Or for TestFlight: npx testflight

或者用于TestFlight:npx testflight

undefined
undefined

Interactive Setup (Alternative)

交互式设置(替代方案)

For interactive mode (requires TTY):
bash
bunx setup-safari
对于交互式模式(需要TTY):
bash
bunx setup-safari

How Universal Links Work

通用链接的工作原理

Universal links require two parts:
  1. AASA file on your server - Tells iOS which paths your app handles
  2. Associated Domains entitlement - Tells your app which domains to claim
通用链接需要两部分:
  1. 服务器上的AASA文件 - 告知iOS你的应用处理哪些路径
  2. 关联域名权限 - 告知你的应用要声明哪些域名

AASA File Format

AASA文件格式

Host at
https://yourdomain.com/.well-known/apple-app-site-association
:
json
{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "TEAM_ID.com.example.app",
        "paths": ["*"]
      }
    ]
  }
}
Path patterns:
  • *
    - Match all paths
  • /products/*
    - Match paths starting with /products/
  • /item/???
    - Match exactly 3 characters after /item/
  • NOT /admin/*
    - Exclude paths
托管在
https://yourdomain.com/.well-known/apple-app-site-association
json
{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "TEAM_ID.com.example.app",
        "paths": ["*"]
      }
    ]
  }
}
路径模式:
  • *
    - 匹配所有路径
  • /products/*
    - 匹配以/products/开头的路径
  • /item/???
    - 匹配/item/后恰好3个字符的路径
  • NOT /admin/*
    - 排除路径

Associated Domains Entitlement

关联域名权限

In your
app.json
or
app.config.js
:
json
{
  "expo": {
    "ios": {
      "associatedDomains": [
        "applinks:yourdomain.com",
        "applinks:www.yourdomain.com"
      ]
    }
  }
}
在你的
app.json
app.config.js
中:
json
{
  "expo": {
    "ios": {
      "associatedDomains": [
        "applinks:yourdomain.com",
        "applinks:www.yourdomain.com"
      ]
    }
  }
}

Testing Universal Links

测试通用链接

After setup, test with setup-safari:
bash
npx setup-safari
Or manually test in Safari:
  1. Open Safari on iOS device/simulator
  2. Navigate to a URL that should open your app
  3. Pull down to reveal the banner, or long-press the link
设置完成后,使用setup-safari进行测试:
bash
npx setup-safari
或者在Safari中手动测试:
  1. 在iOS设备/模拟器上打开Safari
  2. 导航到应打开你的应用的URL
  3. 下拉显示横幅,或长按链接

Testing with Tunnel (No Server Required)

使用隧道测试(无需服务器)

Test universal links without deploying a website using Expo's tunnel feature:
  1. Set a consistent tunnel subdomain:
bash
export EXPO_TUNNEL_SUBDOMAIN=my-app-name
  1. Configure associated domains with the ngrok URL:
json
{
  "expo": {
    "ios": {
      "associatedDomains": ["applinks:my-app-name.ngrok.io"]
    }
  }
}
  1. Build the development client:
bash
npx expo run:ios
  1. Start the dev server with tunnel:
bash
npx expo start --tunnel
  1. Test the link - Type
    https://my-app-name.ngrok.io
    in Safari on your device. It should open your app directly.
The tunnel creates a public HTTPS URL that serves the AASA file automatically, letting you test the full universal links flow during development.
使用Expo的隧道功能,无需部署网站即可测试通用链接:
  1. 设置一致的隧道子域名:
bash
export EXPO_TUNNEL_SUBDOMAIN=my-app-name
  1. 使用ngrok URL配置关联域名:
json
{
  "expo": {
    "ios": {
      "associatedDomains": ["applinks:my-app-name.ngrok.io"]
    }
  }
}
  1. 构建开发客户端:
bash
npx expo run:ios
  1. 启动带隧道的开发服务器:
bash
npx expo start --tunnel
  1. 测试链接 - 在你的设备上的Safari中输入
    https://my-app-name.ngrok.io
    ,它应直接打开你的应用。
隧道会创建一个公共HTTPS URL,自动提供AASA文件,让你可以在开发期间测试完整的通用链接流程。

Debugging

调试

Check if Apple has cached your AASA:
bash
curl "https://app-site-association.cdn-apple.com/a/v1/yourdomain.com"
Validate AASA file format:
bash
curl https://yourdomain.com/.well-known/apple-app-site-association | jq
Common issues:
  • AASA must be served with
    Content-Type: application/json
  • HTTPS required (no self-signed certs)
  • Apple caches AASA files - changes may take time to propagate
检查Apple是否已缓存你的AASA:
bash
curl "https://app-site-association.cdn-apple.com/a/v1/yourdomain.com"
验证AASA文件格式:
bash
curl https://yourdomain.com/.well-known/apple-app-site-association | jq
常见问题:
  • AASA必须以
    Content-Type: application/json
    响应
  • 必须使用HTTPS(不支持自签名证书)
  • Apple会缓存AASA文件,更改可能需要时间才能生效

Expo Router Integration

Expo Router集成

With Expo Router, handle incoming links automatically:
tsx
// app/[...path].tsx handles all deep link paths
// app/products/[id].tsx handles /products/:id links
Access link parameters:
tsx
import { useLocalSearchParams } from "expo-router";

export default function Product() {
  const { id } = useLocalSearchParams();
  return <Text>Product: {id}</Text>;
}
使用Expo Router可自动处理传入链接:
tsx
// app/[...path].tsx 处理所有深度链接路径
// app/products/[id].tsx 处理/products/:id链接
访问链接参数:
tsx
import { useLocalSearchParams } from "expo-router";

export default function Product() {
  const { id } = useLocalSearchParams();
  return <Text>Product: {id}</Text>;
}