# Website + App Clip Plan ## Goal Set up a production website/domain so the BusinessCard App Clip can be invoked reliably from QR codes and links. ## Website Stack (Provided Context) - Frontend: React 18 SPA with Vite and `BrowserRouter` - Hosting: Vercel - Backend endpoint: Vercel Serverless Function (`send-email.js`) - Data-driven content from `site.json` and `apps.json` - App slug on site: `businesscard` ## Confirmed Website Reality (`/website`) - Domain/site: TopDog Labs (`topdoglabs.com`) - Routing: SPA routes in `website/src/App.jsx` (`/`, `/apps`, `/apps/:slug`, `/support`, etc.) - Current Vercel behavior: global rewrite in `website/vercel.json`: - `/(.*)` -> `/index.html` - Impact: this current rewrite will also catch `/.well-known/apple-app-site-association` unless you add exclusions. ## Current Project Context - App currently builds BusinessCard App Clip URLs as: - `https:///appclip/businesscard?id=` - Domain is configured in `BusinessCard/Configuration/Base.xcconfig`: - `APPCLIP_DOMAIN = topdoglabs.com` - App and App Clip entitlements already reference: - `appclips:$(APPCLIP_DOMAIN)` ## What the Website Must Provide 1. A real HTTPS domain you control. 2. Apple App Site Association (AASA) file at: - `https:///.well-known/apple-app-site-association` 3. Correct AASA delivery rules: - No `.json` extension - `Content-Type: application/json` - Publicly accessible (no auth) - No redirects 4. Optional fallback page/endpoint for non-iOS users. ## No Required Language - There is no required backend language for App Clips. - You can use static hosting only, as long as AASA is served correctly. - Use backend/worker only if you want advanced behavior (analytics, non-iOS vCard download, redirects, etc.). ## Does Your Website `businesscard` Slug Matter? - Not for App Clip verification itself. - App Clip invocation for this iOS project uses `/appclip/businesscard?id=...`, not `/apps/:slug`. - Keep `businesscard` slug for your public app page and marketing content. - Ensure `/appclip/businesscard` and `/.well-known/apple-app-site-association` are not intercepted by SPA rewrites. ## AASA File (Starter) Create this file on your website: - Path: `.well-known/apple-app-site-association` Use this template (replace values): ```json { "appclips": { "apps": [ "TEAM_ID.com.mbrucedogs.BusinessCard.Clip" ] } } ``` Notes: - `TEAM_ID` must match your Apple Developer Team ID. - Bundle ID must match your App Clip target bundle ID exactly. ## Vercel + React SPA Implementation When you clone your website repo into this workspace, apply these steps. 1. Add AASA as a static public file: - `public/.well-known/apple-app-site-association` 2. Ensure your SPA fallback rewrite does not override AASA: - Do not rewrite `/.well-known/*` to `index.html` 3. Ensure `/appclip/businesscard` is routed intentionally: - Either serve a dedicated fallback page - Or allow a specific serverless/edge handler for non-iOS behavior 4. Set explicit response header for AASA: - `Content-Type: application/json` ### Example `vercel.json` additions Adjust to merge with your existing config: ```json { "cleanUrls": true, "headers": [ { "source": "/.well-known/apple-app-site-association", "headers": [ { "key": "Content-Type", "value": "application/json" } ] } ], "rewrites": [ { "source": "/.well-known/(.*)", "destination": "/.well-known/$1" }, { "source": "/appclip/businesscard", "destination": "/appclip.html" }, { "source": "/appclip", "destination": "/appclip.html" }, { "source": "/api/(.*)", "destination": "/api/$1" }, { "source": "/(.*)", "destination": "/index.html" } ] } ``` Notes: - Keep `/.well-known/*` before catch-all SPA rewrite. - Keep `/api/*` callable (your contact form endpoint). - If you want `/appclip/businesscard` to show a fallback page, add `website/public/appclip.html`. ## Minimal Files To Add in `website/public` 1. `website/public/.well-known/apple-app-site-association` 2. `website/public/appclip.html` (optional but recommended fallback) `appclip.html` can be minimal: - Message for iPhone users to open via camera/App Clip card - Link for non-iOS users (for example to app page `/apps/businesscard` or App Store URL) - Optional JS to parse `id` query parameter and show it for support/debug ## Multi-App App Clip Pattern (Recommended) For multiple apps, use one shared domain and namespaced paths: - BusinessCard: `/appclip/businesscard?id=` - Rituals: `/appclip/rituals?id=` - SelfieCam: `/appclip/selfie-cam?id=` Keep one AASA file and include all App Clip bundle IDs: ```json { "appclips": { "apps": [ "TEAM_ID.com.mbrucedogs.BusinessCard.Clip", "TEAM_ID.com.mbrucedogs.Rituals.Clip", "TEAM_ID.com.mbrucedogs.SelfieCam.Clip" ] } } ``` Then add one rewrite per App Clip path in `vercel.json`. ## Domain + Hosting Checklist 1. Pick subdomain (recommended): `cards.yourdomain.com` 2. Configure DNS record to your host. 3. Ensure valid TLS certificate (HTTPS). 4. Add/upload AASA file at `/.well-known/apple-app-site-association`. 5. Verify no redirect occurs for that file. ## App Project Changes Required 1. Update domain in `BusinessCard/Configuration/Base.xcconfig`: - `APPCLIP_DOMAIN = cards.yourdomain.com` (recommended) - or use an existing domain if preferred (for example a subpath on `topdoglabs.com`) 2. Confirm entitlements still resolve correctly: - iOS app: `BusinessCard/BusinessCard.entitlements` - App Clip: `BusinessCardClip/BusinessCardClip.entitlements` 3. Clean and rebuild in Xcode after config changes. Note: - A dedicated subdomain (like `cards.topdoglabs.com`) is usually cleaner for App Clip + future deep link infrastructure. ## App Store Connect Steps 1. Open your app in App Store Connect. 2. Create/configure App Clip Experience. 3. Add invocation URL using your real domain/path (for BusinessCard: `/appclip/businesscard`). 4. Submit for review with App Clip metadata. ## Verification Steps (Before Release) 1. Browser check: - Open `https:///.well-known/apple-app-site-association` - Confirm file returns directly (no redirect/auth). 2. Header check (terminal): ```bash curl -I https:///.well-known/apple-app-site-association ``` Confirm: - `HTTP 200` - `content-type: application/json` 3. Device test: - Install app + App Clip build on physical iPhone. - Generate App Clip QR in app share screen. - Scan QR and confirm App Clip opens. - Confirm App Clip can fetch CloudKit card and save contact. 4. Confirm SPA still works: - `/` - `/apps` - `/apps/businesscard` - `/support` - `/.well-known/apple-app-site-association` - `/appclip/businesscard?id=test` ## Optional Fallback (Recommended) For Android/desktop scans: - Host a simple web page or download flow at `/appclip/businesscard`. - Show contact preview or provide `.vcf` download if desired. ## Suggested Rollout Order 1. Domain + AASA live 2. Update `APPCLIP_DOMAIN` 3. Rebuild + physical-device testing 4. App Store Connect App Clip Experience 5. Release ## Risks / Gotchas - Wrong Team ID or bundle ID in AASA breaks invocation. - Redirecting `/.well-known/apple-app-site-association` breaks verification. - Simulator is unreliable for full App Clip invocation testing. - Keep CloudKit schema and public read permissions aligned with App Clip fetch flow.