Dev.to · 3 min read

Opening Web Invite Links Directly in the App with Expo Router

Opening Web Invite Links Directly in the App with Expo Router

This article is an English translation of the original Japanese article. In my club management app, I use the following invite URL for both web and iOS app: https://squad-note.com/invite/{orgId} If the app is installed, Expo Router opens the invite screen in the app. If not, the web page displays. Using Universal Links lets me share a single URL rather than splitting it into web and app versions. Expo Router File Structure I place the invite screen as a dynamic route. apps/mobile/src/app/invite/[orgId]/index.tsx The screen retrieves the orgId from the URL via useLocalSearchParams. import { useLocalSearchParams, useRouter } from "expo-router"; export default function InviteScreen() { const { orgId } = useLocalSearchParams(); const router = useRouter(); const { data: org, isLoading } = api.organization.getPublic.useQuery( { id: orgId! }, { enabled: !!orgId }, ); // Display invite content and execute join process } When opened with /invite/abc, orgId receives abc. I also provide a page with the same path on the web side. Setting a Custom Scheme To handle app-specific URLs, I set a scheme in the Expo config. export default ({ config }: ConfigContext): ExpoConfig => ({ ...config, scheme: "squadnote", }); This allows handling URLs like the following during development and authentication callbacks: squadnote://invite/abc However, I use HTTPS for the invite URLs shared with users. Because custom schemes can be declared by different apps with the same scheme, I use Universal Links as the entry point to securely associate normal web URLs with the app. iOS Associated Domains In app.config.ts, I separate domains for production and development. ios: { bundleIdentifier: IS_PROD ? "com.squadnote.app" : "com.squadnote.app.dev", associatedDomains: IS_PROD ? ["applinks:squad-note.com"] : ["applinks:dev.squad-note.com"], } Adding the configuration alone does not make it work. I also serve apple-app-site-association from the web domain. https://squad-note.com/.well-known/apple-app-site-association On the Next.js side, I placed it as a Route Handler. apps/web/src/app/.well-known/apple-app-site-association/route.ts The response defines the appID combining the Apple Developer Team ID and Bundle ID, and the paths the app should open. To target only the invite link, I can narrow the range to /invite/*. Shared URL On the mobile side, I also share the HTTPS URL. const inviteUrl = `${config.apiUrl}/invite/${id}?openExternalBrowser=1`; await Share.share({ message: `Invitation to ${orgName}\n${inviteUrl}`, }); On iOS devices with the app installed, it opens the app as a Universal Link. Otherwise, it opens the web invite page. openExternalBrowser=1 is a parameter used on the web side for LINE's in-app browser. It is a separate issue from the Universal Links configuration, so I treat app association and web browser workarounds as separate concerns. Verification Considerations Universal Links work with the combination of an installed app, the domain's AASA, and a signed build. Using only Expo Go does not provide the same verification as production. I verify the following separately: Tap URLs in messages with a TestFlight build installed Open the same URL with the app deleted Confirm production and dev domains are not confused Confirm the AASA URL returns JSON without redirects Test with users already in the organization and non-members With Expo Router, paths and file structure correspond, so creating the invite screen inside the app was simple. The time-consuming part was not the app, but associating the domain with iOS. References Expo Router: Link to your app Expo Router: Typed routes

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

Related stories