Bugs de deep link em apps feitas com vibe coding normalmente começam com problemas de input entediantes: um ID de usuário malformado, uma URL de campanha desatualizada, um valor de enum ausente em um payload de push. Aí o app trata esse input como confiável e uma tela quebra no fundo da lógica de happy path.

Todo parâmetro de URL é input do usuário. Deep links, payloads de push notification, URLs compartilhadas, QR codes: tudo isso é não confiável. Quando você faz vibe coding com Cursor, Claude Code gera o happy path lindamente. Ele não gera o caminho paranoico a menos que você o force. Ele assume. Produção pune suposições.

Validação com Zod na Fronteira da Rota

Valide os params exatamente onde eles entram na app. Depois da validação, o resto do código trabalha com valores tipados e garantidamente seguros:

// 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 recebe section e userId como valores devidamente tipados. Ele não sabe nada sobre useLocalSearchParams. Ele não valida. Só recebe dados bons. É assim que você constrói um app feito com vibe coding em que features geradas por IA não quebram quando o mundo real manda lixo.

+native-intent: Rejeite URLs Maliciosas Antes de Elas Entrarem

O Expo Router oferece +native-intent.tsx para lidar com caminhos nativos recebidos que podem estar malformados, desatualizados ou maliciosos. Esse arquivo roda antes de os providers montarem. Ele deveria apenas reescrever paths e rejeitar entradas ruins:

// 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
}

O insight principal: +native-intent não pode verificar se o usuário está logado. Ele só deveria higienizar paths. Autorização de negócio pertence aos route guards, não aqui.

Typed Routes: Pegue Erros em Compile Time

Ative typedRoutes na config e capture nomes de rota digitados errado antes de eles chegarem aos usuários:

// 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 pegam nomes digitados errado. Validação com Zod pega parâmetros ruins. Entre os dois, você tem uma rede de segurança que a maioria dos roteadores web nem oferece.

O Autotomy Expo Starter Pack inclui padrões de validação com Zod, configuração de typed routes e handlers de +native-intent já prontos. Seus deep links funcionam. Seus usuários não caem em crash.