BusinessCard/Website_AppClip_Plan.md

7.3 KiB

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):

{
  "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:

{
  "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

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:

{
  "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):
curl -I https://<your-domain>/.well-known/apple-app-site-association

Confirm:

  • HTTP 200
  • content-type: application/json
  1. 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.
  1. Confirm SPA still works:
  • /
  • /apps
  • /apps/businesscard
  • /support
  • /.well-known/apple-app-site-association
  • /appclip/businesscard?id=test

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.