universal-links
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUse to configure and test universal links (deep links) on iOS.
bunx setup-safari使用在iOS上配置并测试通用链接(深度链接)。
bunx setup-safariImportant: 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 URL scheme and Expo-hosted URLs
exp:// - Custom builds required for your own domain's universal links (applinks:yourdomain.com)
If you only need deep linking for development/testing, try with Expo Go first. Only create custom builds when you need production universal links with your own domain.
npx expo start使用自定义域名的通用链接需要自定义原生构建。 你的应用将无法再在Expo Go中运行。
不过,在开发期间进行基础深度链接时:
- Expo Go支持 URL协议和Expo托管的URL
exp:// - 需要自定义构建才能使用你自己域名的通用链接(applinks:yourdomain.com)
如果你仅在开发/测试时需要深度链接,建议先使用搭配Expo Go。只有当你需要使用自有域名的生产环境通用链接时,再创建自定义构建。
npx expo startAutomated Setup (Recommended)
自动设置(推荐)
Run setup-safari with your Apple ID to automate credential lookup:
bash
EXPO_APPLE_ID="your-apple-id@email.com" bunx setup-safariThis will:
- Authenticate with Apple Developer Portal
- Enable Associated Domains for your bundle ID
- 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这将完成以下操作:
- 与Apple开发者门户进行身份验证
- 为你的bundle ID启用关联域名
- 输出AASA文件内容和元标签
运行完成后,你必须手动执行以下步骤:
1. Create the AASA file
1. 创建AASA文件
Create (no file extension):
public/.well-known/apple-app-site-associationjson
{
"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-associationjson
{
"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
undefinedbash
undefinedDeploy 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
undefinedundefinedInteractive Setup (Alternative)
交互式设置(替代方案)
For interactive mode (requires TTY):
bash
bunx setup-safari对于交互式模式(需要TTY):
bash
bunx setup-safariHow Universal Links Work
通用链接的工作原理
Universal links require two parts:
- AASA file on your server - Tells iOS which paths your app handles
- Associated Domains entitlement - Tells your app which domains to claim
通用链接需要两部分:
- 服务器上的AASA文件 - 告知iOS你的应用处理哪些路径
- 关联域名权限 - 告知你的应用要声明哪些域名
AASA File Format
AASA文件格式
Host at :
https://yourdomain.com/.well-known/apple-app-site-associationjson
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAM_ID.com.example.app",
"paths": ["*"]
}
]
}
}Path patterns:
- - Match all paths
* - - Match paths starting with /products/
/products/* - - Match exactly 3 characters after /item/
/item/??? - - Exclude paths
NOT /admin/*
托管在:
https://yourdomain.com/.well-known/apple-app-site-associationjson
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAM_ID.com.example.app",
"paths": ["*"]
}
]
}
}路径模式:
- - 匹配所有路径
* - - 匹配以/products/开头的路径
/products/* - - 匹配/item/后恰好3个字符的路径
/item/??? - - 排除路径
NOT /admin/*
Associated Domains Entitlement
关联域名权限
In your or :
app.jsonapp.config.jsjson
{
"expo": {
"ios": {
"associatedDomains": [
"applinks:yourdomain.com",
"applinks:www.yourdomain.com"
]
}
}
}在你的或中:
app.jsonapp.config.jsjson
{
"expo": {
"ios": {
"associatedDomains": [
"applinks:yourdomain.com",
"applinks:www.yourdomain.com"
]
}
}
}Testing Universal Links
测试通用链接
After setup, test with setup-safari:
bash
npx setup-safariOr manually test in Safari:
- Open Safari on iOS device/simulator
- Navigate to a URL that should open your app
- Pull down to reveal the banner, or long-press the link
设置完成后,使用setup-safari进行测试:
bash
npx setup-safari或者在Safari中手动测试:
- 在iOS设备/模拟器上打开Safari
- 导航到应打开你的应用的URL
- 下拉显示横幅,或长按链接
Testing with Tunnel (No Server Required)
使用隧道测试(无需服务器)
Test universal links without deploying a website using Expo's tunnel feature:
- Set a consistent tunnel subdomain:
bash
export EXPO_TUNNEL_SUBDOMAIN=my-app-name- Configure associated domains with the ngrok URL:
json
{
"expo": {
"ios": {
"associatedDomains": ["applinks:my-app-name.ngrok.io"]
}
}
}- Build the development client:
bash
npx expo run:ios- Start the dev server with tunnel:
bash
npx expo start --tunnel- Test the link - Type in Safari on your device. It should open your app directly.
https://my-app-name.ngrok.io
The tunnel creates a public HTTPS URL that serves the AASA file automatically, letting you test the full universal links flow during development.
使用Expo的隧道功能,无需部署网站即可测试通用链接:
- 设置一致的隧道子域名:
bash
export EXPO_TUNNEL_SUBDOMAIN=my-app-name- 使用ngrok URL配置关联域名:
json
{
"expo": {
"ios": {
"associatedDomains": ["applinks:my-app-name.ngrok.io"]
}
}
}- 构建开发客户端:
bash
npx expo run:ios- 启动带隧道的开发服务器:
bash
npx expo start --tunnel- 测试链接 - 在你的设备上的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 | jqCommon 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 linksAccess 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>;
}