Допустим, пользователь получил письмо с кнопкой «Посмотреть заказ». Он нажимает — и что происходит? Открывается браузер, показывается страница входа, человек вводит пароль, ищет нужный заказ... Раздражает. А могло быть иначе: нажал — приложение открылось сразу на нужном экране. Это и есть deeplink.
Deeplink — это ссылка, которая ведёт не просто в приложение, а в конкретное место внутри него. На карточку товара, в чат, на экран оплаты. Звучит просто, но под капотом — два разных механизма со своими плюсами, минусами и подводными камнями.
Два способа сделать deeplink в iOS
URL Schemes — старый добрый способ
Custom URL Scheme — это когда приложение регистрирует себе кастомный протокол вместо https. Например, myapp://product/123. iOS видит такую ссылку и знает: её должно открыть конкретное приложение.
Настраивается элементарно. В Info.plist добавляешь:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
Потом в AppDelegate или через SceneDelegate ловишь вызов:
func application(_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
// разбираем url и роутим
return true
}
Звучит хорошо, но есть проблемы.
Первая: схема не уникальна. Любое приложение может зарегистрировать myapp://. Если на устройстве окажется два приложения с одинаковой схемой, iOS выберет одно из них — и это не обязательно твоё. На практике крупные компании бронируют схемы вроде fb:// или twitter://, но никаких гарантий нет.
Вторая: если приложение не установлено, URL Scheme просто падает. Браузер показывает ошибку, пользователь недоумевает. Никакого фоллбэка нет из коробки.
Третья: в Safari с iOS 9 нельзя просто вызвать myapp:// через JavaScript — браузер покажет алерт с вопросом. Это раздражает пользователя.
URL Schemes до сих пор используют для межприложечной коммуникации — когда одно твоё приложение открывает другое твоё. Но для связки сайт-приложение лучше Universal Links.
Universal Links — современный подход
Universal Links работают иначе. Ты берёшь обычную HTTPS-ссылку — например, https://myapp.ru/product/123 — и говоришь iOS: «Если это приложение установлено, открой ссылку в нём. Если нет — открой в браузере».
Плюсы очевидны:
- Одна ссылка работает везде — в приложении, в браузере, в письме
- Если приложение не установлено, человек попадает на сайт (нормальный фоллбэк)
- Схему нельзя «угнать», потому что ты подтверждаешь владение доменом
- Работает без алертов
Настройка сложнее, но один раз — и работает надёжно.
Как настроить Universal Links
Шаг 1: файл apple-app-site-association
На сервере, в корне домена или по пути /.well-known/, нужно разместить файл apple-app-site-association (без расширения). iOS запрашивает его при установке приложения.
Содержимое файла:
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAMID.com.yourcompany.yourapp",
"paths": [
"/product/*",
"/order/*",
"/user/profile"
]
}
]
}
}
appID — это Team ID из Apple Developer + bundle identifier приложения. Найти Team ID можно в developer.apple.com в разделе Membership.
paths — паттерны URL, которые должны открываться в приложении. Звёздочка — wildcard. Можно исключать пути через NOT:
"paths": [
"/product/*",
"NOT /product/sale"
]
Важный момент: файл должен отдаваться с Content-Type: application/json и быть доступен по HTTPS без редиректов. iOS не пойдёт по редиректам при его загрузке.
С iOS 13 Apple перешла на CDN — они кешируют файл сами. Это значит, что изменения применяются не мгновенно. Обновление может занять от нескольких часов до суток.
Шаг 2: настройка в Xcode
В Xcode открываешь Target → Signing & Capabilities → добавляешь Associated Domains. Туда вписываешь:
applinks:myapp.ru
Если нужна поддержка нескольких доменов — добавляешь несколько строк:
applinks:myapp.ru
applinks:www.myapp.ru
Xcode автоматически добавит нужные entitlements в сборку.
Шаг 3: обработка в приложении
В SceneDelegate:
func scene(_ scene: UIScene,
continue userActivity: NSUserActivity) {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else { return }
handleDeeplink(url: url)
}
Если приложение открывается с нуля (не из фона):
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
if let userActivity = connectionOptions.userActivities.first,
userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL {
handleDeeplink(url: url)
}
}
Сама функция роутинга — это уже твоя логика. Можно парсить URL вручную или использовать библиотеки вроде URLNavigator.
Роутинг: как правильно разбирать ссылки
Простой вариант — switch по path:
func handleDeeplink(url: URL) {
let components = URLComponents(url: url, resolvingAgainstBaseURL: true)
let path = url.path
switch path {
case let p where p.hasPrefix("/product/"):
let id = String(p.dropFirst("/product/".count))
showProduct(id: id)
case "/cart":
showCart()
case "/profile":
showProfile()
default:
showHome()
}
}
Для более сложных случаев лучше использовать паттерн Router или Coordinator, чтобы не захламлять AppDelegate. Deeplink должен уметь открыть нужный экран из любого состояния приложения — даже если пользователь был на другом разделе.
Частая ошибка: разработчик делает deeplink только для «свежего» запуска приложения, но забывает про кейс, когда приложение уже открыто. Тогда scene(_:continue:) не вызывается — нужно отдельно обрабатывать scene(_:openURLContexts:) для URL Schemes или использовать NSUserActivity через application(_:continue:restorationHandler:) в старом AppDelegate-подходе.
Как связать сайт с приложением
На сайте всё просто: используешь обычные HTTPS-ссылки. Если приложение установлено — iOS перехватит их. Если нет — человек попадёт на сайт.
Но часто нужно сделать умную кнопку «Открыть в приложении» или баннер. Для этого есть несколько подходов.
Smart App Banners — встроенный механизм iOS. В <head> страницы добавляешь:
<meta name="apple-itunes-app"
content="app-id=123456789, app-argument=https://myapp.ru/product/123">
Safari покажет баннер сверху с кнопкой «Открыть» или «Загрузить». Выглядит нативно, не требует JavaScript. app-argument — это deeplink, который будет передан в приложение.
JavaScript + проверка — более гибкий вариант. Можно попробовать открыть URL Scheme через window.location, и если через 1-2 секунды пользователь всё ещё на странице — значит, приложение не установлено, перенаправляем в App Store. Это хак, который работает, но не всегда надёжно.
Branch.io, Firebase Dynamic Links — готовые сервисы для deferred deeplinks. Они решают одну важную проблему: что если пользователь нажал ссылку, но приложение ещё не установлено? После установки нужно открыть тот же экран. Это называется deferred deeplink — отложенный deeplink.
Firebase Dynamic Links, к сожалению, были закрыты в 2025 году. Сейчас популярные альтернативы — Branch.io и Appsflyer. Они создают «умные» ссылки, которые:
- Определяют платформу (iOS/Android/desktop)
- Сохраняют параметры до установки приложения
- После установки «раскрывают» отложенный deeplink
- Дают аналитику по переходам
Интеграция с внешними сервисами
Deeplinks нужны не только для сайта. Вот типичные сценарии:
Push-уведомления — самый частый кейс. В payload уведомления передаёшь deeplink:
{
"aps": {
"alert": "Ваш заказ отправлен"
},
"deeplink": "https://myapp.ru/order/456"
}
В обработчике уведомления достаёшь этот URL и роутишь. Пользователь нажимает на уведомление — попадает сразу на заказ.
Email-рассылки — вставляешь Universal Link в письмо. Большинство почтовых клиентов на iOS открывают ссылки через Safari/браузер, и Universal Links работают. Исключение — некоторые клиенты открывают ссылки во встроенном WebView, и там Universal Links могут не сработать.
QR-коды — генерируешь QR с Universal Link. Стандартная камера iOS умеет их открывать прямо в приложении, без перехода в браузер.
Межприложечная интеграция — если нужно, чтобы другое приложение открыло твоё, можно использовать как URL Schemes (если договорились о схеме), так и Universal Links (если у тебя есть домен).
Частые ошибки и как их избежать
Файл apple-app-site-association недоступен или отдаёт редирект. iOS не пойдёт по редиректам. Проверить можно через curl -I https://yourdomain.ru/apple-app-site-association — должен быть 200, Content-Type: application/json.
Неправильный Team ID. Встречается часто. Team ID — это 10-значный идентификатор вроде A1B2C3D4E5, не путай с App ID Prefix.
Пути в AASA не совпадают с реальными URL. Если в файле написано /product/*, а реальные ссылки идут как /products/123 — не работает. Паттерны чувствительны к точности.
Обработка только при холодном старте. Обязательно проверь, что deeplink работает, когда приложение уже запущено в фоне.
Отсутствие фоллбэка. Если Universal Link не сработал (например, нет интернета при установке, и AASA не загрузился), ссылка откроется в браузере. Убедись, что страница на сайте нормально отображается и содержит кнопку «Открыть в приложении».
Тестирование
Проверять deeplinks удобно несколькими способами:
Через Notes — напиши ссылку в заметках, нажми долгим тапом, выбери «Открыть ссылку». Быстро и без лишних инструментов.
Через Safari на устройстве — просто открой URL.
Через терминал с симулятором:
xcrun simctl openurl booted "https://myapp.ru/product/123"
Через Xcode — Debug → Simulate Location / Push Notifications не поможет для deeplinks, но можно добавить launch argument:
-deeplink https://myapp.ru/product/123
И в коде в didFinishLaunching проверять этот аргумент — удобно для разработки.
Для проверки самого файла AASA есть официальный валидатор от Apple — ищи по запросу «AASA validator». Он проверит формат и доступность файла.
Когда это особенно важно для бизнеса
Deeplinks — не техническая деталь, это прямо влияет на конверсию. Исследования показывают, что пользователи, попавшие в нужное место приложения сразу, конвертируются в 2-3 раза лучше, чем те, кого бросили на главный экран.
Для интернет-магазинов это критично: человек видит товар в Instagram, нажимает ссылку, попадает на карточку товара в приложении с уже добавленным в корзину продуктом — это совсем другая история, чем «попади на главную и поищи сам».
Если ты заказываешь разработку мобильного приложения или интернет-магазина — спроси подрядчика заранее, как будут реализованы deeplinks и deferred deeplinks. Это должно быть частью проекта с самого начала, а не «доделаем потом». В REEXY, например, интеграцию deeplinks закладывают в техническое задание ещё на этапе проектирования, чтобы не переделывать архитектуру роутинга на середине разработки.
Что проверить перед релизом
- Файл AASA доступен на всех поддоменах, которые нужны
- Все паттерны путей прописаны и протестированы
- Роутер обрабатывает все возможные deeplink-URL, включая невалидные (без краша)
- Deeplink работает при холодном старте и при переходе из фона
- Фоллбэк на сайте работает корректно
- Push-уведомления с deeplink протестированы на реальном устройстве
- Deferred deeplink (если нужен) работает после чистой установки
Deeplinks — один из тех механизмов, который не виден пользователю напрямую, но сильно влияет на его опыт. Сделал правильно — люди не замечают и просто пользуются. Сделал плохо — получаешь негативные отзывы и потери в конверсии.