푸시 알림을 붙이고 나서 다음 요구가 생겼다. 알림을 탭하면 앱이 그냥 켜지는 게 아니라, 해당 주문 상세 화면으로 바로 들어가야 한다. 웹이라면 URL 하나로 끝날 일인데 네이티브 앱은 이 "링크로 특정 화면 열기"가 딥링크라는 별도 세팅을 요구한다.
처음엔 라이브러리를 이것저것 찾아봤는데, Expo Router를 쓰고 있으면 사실상 추가로 설치할 게 거의 없다. 라우팅이 이미 파일 기반이라 URL 경로와 화면이 1:1로 매핑되기 때문이다.
스킴 등록
먼저 앱만의 URL 스킴을 정한다. app.json에 한 줄이면 된다.
{
"expo": {
"scheme": "myapp"
}
}
이러면 myapp:// 로 시작하는 링크가 내 앱을 가리키게 된다. myapp://orders/42 는 앱을 열고 app/orders/[id].tsx 화면으로 이동한다. Expo Router가 경로를 알아서 파싱해주니 별도 라우팅 코드가 필요 없다. 여기까지가 핵심이고, 나머지는 세부 처리다.
링크 만들고 열기
앱 내부에서 딥링크 URL을 만들 땐 expo-linking의 createURL을 쓰면 개발/프로덕션 환경에 맞는 스킴을 알아서 붙여준다.
import * as Linking from 'expo-linking';
const url = Linking.createURL('/orders/42');
// 개발: exp://192.168.0.5:8081/--/orders/42
// 프로덕션: myapp://orders/42
개발 환경에서 스킴이 다르다는 게 처음에 헷갈리는 부분이다. createURL을 쓰면 이걸 신경 안 써도 된다.
앱이 꺼져 있을 때 vs 켜져 있을 때
여기서 한 번 걸렸다. 딥링크는 두 가지 경우를 나눠서 처리해야 한다.
- 앱이 완전히 꺼진 상태에서 링크로 실행 →
useURL()이 초기 URL을 잡아준다. - 앱이 백그라운드에 떠 있는 상태에서 링크 수신 → 같은 훅이 새 URL을 흘려준다.
Expo Router는 대부분 이걸 알아서 화면 이동으로 연결해주지만, 링크에 담긴 파라미터로 추가 로직(예: 로그인 체크)을 돌리고 싶을 때 훅으로 직접 값을 읽는다.
import { useURL } from 'expo-linking';
import { useEffect } from 'react';
export default function App() {
const url = useURL();
useEffect(() => {
if (url) {
const { path, queryParams } = Linking.parse(url);
console.log('열린 경로:', path, queryParams);
}
}, [url]);
}
실제 기기에서 확인
시뮬레이터에서 터미널로 바로 링크를 쏴볼 수 있다.
npx uri-scheme open "myapp://orders/42" --ios
마무리
Expo Router를 쓰고 있다면 딥링크는 스킴 등록 한 줄로 8할이 끝난다. 남는 건 꺼진 상태/떠 있는 상태 구분과, 개발 환경 스킴이 다르다는 점 정도. iOS 유니버설 링크(https:// 도메인 연결)는 도메인 소유 인증이 추가로 필요하니, 그건 스킴 딥링크가 잘 돌아가는 걸 확인한 다음 붙이는 걸 추천한다.