215 lines
7.3 KiB
Markdown
215 lines
7.3 KiB
Markdown
# 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_DOMAIN>/appclip/businesscard?id=<recordName>`
|
|
- 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://<your-domain>/.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=<id>`
|
|
- Rituals: `/appclip/rituals?id=<id>`
|
|
- SelfieCam: `/appclip/selfie-cam?id=<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://<your-domain>/.well-known/apple-app-site-association`
|
|
- Confirm file returns directly (no redirect/auth).
|
|
2. Header check (terminal):
|
|
|
|
```bash
|
|
curl -I https://<your-domain>/.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.
|