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.delaynumber3000倒數時長(毫秒,見 TIME-001)。到點執行 location.href = url;傳 0 或負數 = 掛載後立即跳轉
options.fallbackstring'/'query 裡沒有合法 http(s) 外部連結時的回退目標(同樣透傳 safeExternalUrl)
options.queryKeystring'url'讀哪個 query 參數
options.seo{ title?: string; description?: string }內建中文文案覆蓋預設 SEO 文案。⚠️ robots: 'noindex' 與 schemaOrg: false 固定,不可覆蓋

回傳值

名稱型別說明
urlstring經白名單校驗後的目標網址;非法則等於 fallback
remainingRef<number>剩餘毫秒
secondsComputedRef<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 不提供——各站視覺差異大,只收邏輯不收外觀。

相關連結