当坏链接进入 production
Vibe-coded app 里的 deep link bug,通常都始于一些很无聊的 input 问题:malformed user ID、过期的 campaign URL、push payload 里缺失的 enum 值。接着 app 把这些 input 当成可信数据,screen 就会在 happy-path logic 的深处直接 crash。
所有 URL parameter 本质上都是 user input。deep link、push notification payload、shared URL、QR code,它们全都不可信。你用 Cursor 做 Vibe coding 时,Claude Code 会把 happy path 生成得很好,但 paranoid path 不会自己出现,除非你明确要求。它会 assume。production 会惩罚 assumption。
在 Route Boundary 用 Zod Validation
param 应该在刚进入 app 的那一刻就被 validate。过了这一层之后,其余代码只处理 typed 且 guaranteed-safe 的值。
// 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 收到的 section 和 userId 都已经是正确类型、可安全使用的值。它不需要知道 useLocalSearchParams,也不需要自己 validate。它只接收干净数据。这才是构建 Vibe-coded app 的方式:即使真实世界给你喂的是垃圾输入,AI 生成的 feature 也不会被带崩。
+native-intent:在坏 URL 进来之前先拦下它
Expo Router 提供了 +native-intent.tsx,专门处理那些可能 malformed、stale 或 malicious 的 native path。这个 file 会在 provider mount 之前 执行。它应该只做 path rewrite 和坏输入的 reject。
// 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
}
关键点在这里:+native-intent 没法检查用户是不是已经 logged in。它只应该负责 sanitize path。business authorization 属于 route guard,不属于这里。
Typed Routes:在 compile time 把错误拦住
在 config 里打开 typedRoutes,把 route 名字拼错这种错误挡在用户看到之前。
// 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 能抓 route 名拼写错误,Zod validation 能抓坏参数。两者配合起来,你会得到一层很多 web router 都没有的 safety net。
Autotomy Expo Starter Pack 已经把 Zod validation pattern、typed route config 和 +native-intent handler 配好了。你的 deep link 能正常工作,用户不会因此 crash。