Deep-Link-Bugs in vibe-codierten Apps beginnen meist mit langweiligen Input-Problemen: einer fehlerhaften User-ID, einer veralteten Campaign-URL, einem fehlenden Enum-Wert in einem Push-Payload. Dann behandelt die App diesen Input als vertrauenswürdig - und ein Screen crasht tief im Happy-Path-Code.

Jeder URL-Parameter ist User Input. Deep Links, Push-Notification-Payloads, geteilte URLs, QR-Codes - all das ist nicht vertrauenswürdig. Wenn du mit Cursor vibe codest, generiert Claude Code den Happy Path wunderschön. Den paranoiden Pfad generiert es nur, wenn du ihn verlangst. Es nimmt an. Production bestraft Annahmen.

Zod-Validierung an der Route Boundary

Validiere Parameter genau dort, wo sie in die App eintreten. Nach der Validierung arbeitet der Rest deines Codes mit typisierten, garantiert sicheren Werten:

// app/(app)/profile.tsx
// Validate at the gate. Everything past this point is typed.

import { useLocalSearchParams } from 'expo-router'
import { z } from 'zod'

const profileParamsSchema = z.object({
  section: z
    .enum(['overview', 'security', 'preferences'])
    .default('overview'),
  userId: z.string().uuid().optional(),
})

type ProfileParams = z.infer<typeof profileParamsSchema>

export default function ProfileScreen() {
  const raw = useLocalSearchParams()
  const result = profileParamsSchema.safeParse(raw)

  if (!result.success) {
    return <ErrorScreen message='Invalid profile parameters' />
  }

  const { section, userId } = result.data
  return <ProfileContent section={section} userId={userId} />
}

ProfileContent bekommt section und userId als sauber typisierte Werte. Es weiß nichts über useLocalSearchParams. Es validiert nicht. Es bekommt einfach nur gute Daten. So baust du eine vibe-codierte App, in der KI-generierte Features nicht zerbrechen, sobald die reale Welt ihnen Müll schickt.

+native-intent: Bösartige URLs ablehnen, bevor sie hereinkommen

Expo Router gibt dir +native-intent.tsx, um eingehende native Pfade zu behandeln, die fehlerhaft, veraltet oder bösartig sein können. Diese Datei läuft bevor deine Provider mounten. Sie sollte nur Pfade umschreiben und schlechte Eingaben ablehnen:

// app/+native-intent.tsx
// This is your firewall, not your business logic.

export function redirectSystemPath({
  path,
}: {
  path: string
}) {
  // Reject paths with suspicious patterns
  if (path.includes('..') || path.includes('//')) {
    return '/'
  }

  // Migrate old URL format to new format
  if (path.startsWith('/user/')) {
    const userId = path.replace('/user/', '')
    if (z.string().uuid().safeParse(userId).success) {
      return `/profile?userId=${userId}`
    }
    return '/'
  }

  return path
}

Die zentrale Erkenntnis: +native-intent kann nicht prüfen, ob der Nutzer eingeloggt ist. Es sollte nur Pfade sanitizen. Business Authorization gehört in Route Guards, nicht hierher.

Typed Routes: Fehler zur Compile-Zeit abfangen

Aktiviere typedRoutes in deiner Config und fange falsch geschriebene Route-Namen ab, bevor sie Nutzer erreichen:

// app.config.ts
{
  experiments: {
    typedRoutes: true,
  }
}

// Now the router validates at compile time:
router.push('/profile')         // OK
router.push('/profle')          // TypeScript error
router.push({
  pathname: '/profile',
  params: { section: 'overview' },
})  // OK

Typed Routes fangen falsch geschriebene Namen ab. Zod-Validierung fängt schlechte Parameter ab. Zusammen hast du damit ein Sicherheitsnetz, das die meisten Web-Router gar nicht bieten.

Das Autotomy Expo Starter Pack bringt Zod-Validierungspatterns, Typed-Route-Config und +native-intent-Handler bereits vorkonfiguriert mit. Deine Deep Links funktionieren. Deine Nutzer crashen nicht.