useExternalRedirect()
useExternalRedirect 是「外链中转页」(/link?url=https://example.com)的逻辑层:读 query 里的目标地址、过安全白名单、倒计时后跳转,同时把这类页面必须做对的 SEO 设置固定住。
中转页要同时做对三件事,而每一件做错当场都看不见:
| 要做对的事 | 做错的后果 | 谁负责 |
|---|---|---|
目标 URL 过 safeExternalUrl 白名单 | 漏了 = javascript: 被赋给 location.href 的 DOM XSS | ✅ 本 composable 内置 |
robots: 'noindex' | 漏了 = 中转页被搜索引擎索引 | ✅ 本 composable 内置(不可覆盖) |
页面声明 defineRouteRules({ ssr: false }) | 漏了 = 依赖 query 的页面被 ISR 缓存成第一个访客的目标地址 | 🔴 你自己写(见下) |
收编自 9 个 applier 各自的
pages/link.vue(framework#Y-9)。starter 那份历史上改过 12 次,而 applier 副本只跟到第 2 次——补丁不会自动传播,所以把逻辑收进框架、模板留给 applier 自绘。
🔴 页面必须自己声明 ssr: false
这一条 composable 管不到:defineRouteRules 是 Nuxt 的编译期宏,只能写在页面文件里。
<!-- pages/link.vue -->
<script setup lang="ts">
defineRouteRules({ ssr: false }) // 🔴 不能省
definePageMeta({ layout: false })
const { url, seconds } = useExternalRedirect()
</script>
<template>
<p>即将前往:<a :href="url">{{ url }}</a></p>
<p><strong>{{ seconds }}</strong> 秒后自动跳转</p>
</template>
漏写会怎样:中转页的内容完全由 ?url= 决定,一旦被 ISR / SWR 缓存,后面所有访客拿到的都是第一个访客的目标地址——这不只是显示错,是把 A 用户的跳转目标发给了 B 用户。
兜底:dev 期该 composable 在服务端执行到时会 console.warn 点名这件事(生产不打印)。但它只在你真的跑到那条路径时才出现,别指望它替你把关。
参数
useExternalRedirect(options?)
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
options.delay | number | 3000 | 倒计时时长(毫秒,见 TIME-001)。到点执行 location.href = url;传 0 或负数 = 挂载后立即跳转 |
options.fallback | string | '/' | query 里没有合法 http(s) 外链时的回退目标(同样透传 safeExternalUrl) |
options.queryKey | string | 'url' | 读哪个 query 参数 |
options.seo | { title?: string; description?: string } | 内置中文文案 | 覆盖默认 SEO 文案。⚠️ robots: 'noindex' 与 schemaOrg: false 固定,不可覆盖 |
返回值
| 名称 | 类型 | 说明 |
|---|---|---|
url | string | 经白名单校验后的目标地址;非法则等于 fallback |
remaining | Ref<number> | 剩余毫秒 |
seconds | ComputedRef<number> | 剩余整秒(向上取整),供「N 秒后跳转」文案直接用 |
go | () => void | 立即跳转(用户点「立即前往」时调) |
cancel | () => void | 取消自动跳转(停表;url 仍可点击) |
使用示例
<!-- 示例1:最小可用中转页 -->
<script setup lang="ts">
defineRouteRules({ ssr: false })
const { url, seconds } = useExternalRedirect()
</script>
<template>
<a :href="url">{{ url }}</a>
<p>{{ seconds }} 秒后自动跳转</p>
</template>
<!-- 示例2:给用户「立即前往 / 取消」两个出口 -->
<script setup lang="ts">
defineRouteRules({ ssr: false })
const { url, seconds, go, cancel } = useExternalRedirect({ delay: 5000 })
</script>
<template>
<p>{{ seconds }} 秒后前往 {{ url }}</p>
<SwButton @click="go">立即前往</SwButton>
<SwButton variant="ghost" @click="cancel">留在本站</SwButton>
</template>
// 示例3:改倒计时 / 回退路径 / query 参数名 / 文案
useExternalRedirect({
delay: 5000, // 毫秒
fallback: '/home', // 没有合法外链时回这里
queryKey: 'target', // 读 ?target= 而不是 ?url=
seo: { title: 'Redirecting…', description: 'Leaving the site' },
})
注意事项
delay是毫秒,不是秒。倒计时文案用返回的seconds(已向上取整),别自己拿delay去除。robots/schemaOrg不开放覆盖是有意的:中转页被索引、或带上 JSON-LD,都属于这类页面的典型错误,没有合理的反例场景。fallback也会过白名单,所以传外站地址同样受约束;想回本站写相对路径即可。- 页面模板(样式、品牌、「立即前往」按钮)framework 不提供——各站视觉差异大,只收逻辑不收皮肤。
相关链接
- safeExternalUrl —— 底层的外链白名单校验
- useSeo —— 本 composable 内部用它设
robots/ 文案 - 渲染策略 · 为什么中转页必须
ssr: false