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