瀏覽器端的 hreflang 產生器與撰寫良好的命令列腳本產生相同的跳脫後 HTML link 元素,但兩種方式在校驗紀律、部署機制與可審核性上差異極大。命令列產生方式適合搭配批次工作流程與版本控制及模板化,而線上產生器則擅長在不撰寫程式碼的情況下提供速度、跳脫正確性與重複偵測。Lizely 的 Hreflang 產生器 屬於線上這一派:它最多接受 100 列 locale|URL 資料列、會標準化大小寫(因此 en-us 會變成 en-US)、明確處理 x-default,並在輸入格式錯誤時直接拒絕整個集合,而不是只輸出部分區塊。Python 或 Node 腳本同樣能做到這些,但前提是你必須自己撰寫語系解析、HTML 跳脫與重複拒絕邏輯,並記得讓同一語系群組中每個頁面的輸出保持完全一致。無論是線上或腳本方式,都無法證明備援頁面真的回傳有用的 200 回應;這個檢查仍需要在部署後實際爬取線上頁面。
產生 Hreflang 標籤的兩種方式
大多數需要 hreflang 標籤的團隊會在兩條路徑之間擇一:在終端機執行的小腳本,或回傳可直接貼上 HTML 的瀏覽器端表單。兩者最終都能產生正確的輸出,但失敗模式不同。腳本的錯誤往往悄無聲息——大小寫錯誤、ampersand 跳脫遺漏、缺少自身連結——而且因為建置仍然成功,於是就直接上線了。線上產生器將這些檢查集中到單一解析器中,但會增加開啟分頁與手動複製輸出的摩擦。正確的選擇比較不在於誰比較「好」,而是在於你的頁面如何建置、語系列表多久變動一次。
命令列 Hreflang 腳本的典型運作方式
在命令列的世界中,hreflang 產生通常呈現三種面貌之一。第一種是讀取 locale|URL 資料列檔案的 Python 或 Node 小工具,逐行解析、標準化語系、跳脫屬性敏感字元,然後將 link 區塊輸出到 stdout 或檔案。第二種是使用 awk、sed 或 jq 的 shell 管線,從 CSV 或結構化設定檔產生標籤;這種方式適用於非常小的群組,但當語系包含像 zh-Hant 這類文字子標籤時,很快就會變得脆弱。第三種是靜態網站產生器(Hugo、Jekyll、Eleventy)中的建置步驟,語系資料已存在於 front matter 中,模板會在其上迴圈以將 hreflang 元素注入 head。
困難的部分不是迴圈本身,而是驗證:en-us 必須變成 en-US、URL 中的片段(fragments)與認證資訊必須被移除、x-default 至多只能出現一次、重複的語系資料列必須以不區分大小寫的方式被拒絕,以及 URL 中的 ampersand 必須在 HTML 輸出前完成跳脫。任何一個步驟被省略的腳本仍會產生標籤;只是輸出結果可能不符合 Google 對本地化版本所記載的預期,或不符合 WHATWG HTML alternate links 規範——該規範規範了文件中帶 hreflang 的 rel="alternate" 之表示方式。
使用瀏覽器端工具產生 Hreflang 標籤
對沒有腳本的團隊來說,路徑就是開啟瀏覽器端產生器,貼上乾淨的清單,然後把結果複製到每個頁面的 head 中。以下是根據該工具文件記載的操作步驟所整理的確切機制:
- 將每個本地化頁面列為一列「locale | 完整 URL」資料列,包括你正在編輯的頁面,以及在確實存在通用登陸頁時可選用的 x-default 備援。
- 將資料列貼入 Hreflang 產生器。每行必須只包含一個直立分隔符,且集合中至少要有兩列。
- 確認工具已將語系大小寫標準化(en-us 變成 en-US,zh-hant 變成 zh-Hant),且每個 URL 皆為以 http 或 https 開頭的完整網址,不得包含相對路徑、不含通訊協定相對參照、不含片段(fragments)也不含內嵌認證資訊。
- 複製產生的跳脫後 link 區塊。該工具每列會回傳一個 rel=alternate 元素,屬性敏感字元已完成 HTML 跳脫,可直接用於頁面 head。
- 在語系群組中每個頁面的 head 中安裝完全相同的區塊,包括指向每個頁面自身的連結,並確認備援頁面彼此互相指向。
- 從每個語系中爬取一個樣本,並驗證自我參照、互相回指的連結,以及每個備援頁面皆回傳有用的 200 回應。
該工具將輸入上限設為 100 列,並在任何一列格式錯誤時拒絕整個集合,這能避免在複製區塊時靜默遺漏某個國家或語言版本,導致只複製到部分區塊。這種「遇錯即停」的行為是生產環境使用上最重要的特性;腳本可以模仿這種行為,但預設情況下很少會這樣做。
命令列與線上:坦率的權衡
| 考量面向 | 命令列腳本 | 瀏覽器端產生器 |
|---|---|---|
| 語系大小寫標準化 | 需自行實作(如 Intl.getCanonicalLocales) | 內建 |
| URL 標準化(不含片段、不含認證資訊) | 需自行實作 | 內建 |
| 重複語系偵測 | 需自行實作 | 內建 |
| x-default 處理 | 需自行實作 | 內建 |
| HTML 屬性跳脫 | 需自行實作 | 內建 |
| 產生輸出的版本控制 | 原生適合 | 手動貼上,無 diff 紀錄 |
| 與靜態網站產生器的整合 | 原生適合 | 需要額外的建置步驟 |
| 臨時性單頁編輯 | 殺雞用牛刀 | 快速 |
| 對線上頁面的互相回指連結進行稽核 | 需要外部爬蟲 | 需要外部爬蟲 |
這個模式反覆出現:命令列產生方式把工作集中到你的版本庫中,但每一道防護都是你必須撰寫並維護的程式碼。瀏覽器端產生方式把這些防護推進工具內,但部署工作則落到操作人員身上,由其將輸出貼回 CMS 或模板中。對於在不同情境下面臨相同權衡的團隊,關於 canonical 標籤產生的比較也呈現幾乎相同的格局。
| 輸入資料列 | 標準化後的語系 | 備註 |
|---|---|---|
| en | https://example.com/en/ | en | 通用語言頁面,可作為備援 |
| en-us | https://example.com/en-us/ | en-US | 區域標籤已標準化為大寫 |
| zh-hant | https://example.com/zh-hant/ | zh-Hant | 文字子標籤已標準化為首字母大寫 |
| pt-BR | https://example.com/pt-br/ | pt-BR | 區域保留為標準大寫 |
| x-default | https://example.com/ | x-default | 每個群組僅一個;不是一種語言 |
命令列產生方式更適合的時機
當語系列表與其他網站設定一起存放在原始碼控制中時,以腳本為基礎的做法就值得採用。像 Hugo、Jekyll 與 Eleventy 等靜態網站產生器原本就提供各語系的資料,因此一個能在這些資料上迴圈並輸出 hreflang link 元素的模板,是最具可重現性的路徑:修改語系檔案、重新建置,每個頁面就會拿到一致的標籤組。在部署前對輸出進行檢查的 CI 管線能捕捉到大多數的回歸問題。擁有數十種語言的大型群組受益最大,因為另一種替代方案——手動將產生的區塊貼入數十個 CMS 項目——本身就會成為飄移的來源。
當你需要將同一個 hreflang 區塊同時以 HTTP 標頭或 XML sitemap 條目形式輸出(而不只是 HTML head 標籤)時,腳本同樣是正確答案。雖然 Google Search Central 關於本地化版本的指引將這三種實作方式視為等價,但在同一個群組中混用正是造成飄移的原因;以程式碼作為唯一真實來源,是讓它們保持一致最簡單的方式。
瀏覽器端產生器更適合的時機
當語系列表很短、發布系統不會模板化 head 元素,或工作只是針對少數頁面的一次性編輯時,線上做法就會勝出。一位行銷人員在五種語系的 WordPress 網站上新增一個西班牙文登陸頁,並不需要為了這個單一改動去準備 Python 工具。把五列資料貼進瀏覽器端工具、複製輸出、然後放進頁面 head(以及其餘四個頁面的 head 中),就能快速完成工作。
對於還不確定語系組合應該長什麼樣的團隊來說,這也是正確的起點。該工具的內建標準化會在程式碼進入頁面之前就浮現大小寫與格式錯誤,並對格式錯誤的輸入直接拒絕整個集合,提供「有東西需要修正」的即時訊號。同樣的特性也適用於 canonical 標籤輸出、schema 標記,或任何其他小型跳脫 HTML 區塊;在貼入那一刻的正確性,比管線的優雅更為重要。
會讓兩種方式都失效的常見錯誤
最消耗檢索預算的錯誤在比較的兩邊其實是相同的。忘記把當前頁面納入集合是最常見的一個。Google 將自我參照記載為必要訊號;缺少當前頁面的集合會被視為單向宣告,因而可能被忽略。讓各備援頁面回傳不同的 hreflang 區塊是下一個錯誤:每個備援頁面都必須發布相同的完整集合,包括指回其他所有備援頁面的連結。在沒有真正備援頁面的情況下加入 x-default,或在同一集合中重複 x-default 資料列,會產生結構上看似整齊但仍傳達搜尋引擎無法兌現之關聯的程式碼。
其他反覆出現的錯誤還包括:使用相對 URL 或通訊協定相對參照(工具會拒絕兩者,而搜尋引擎在 hreflang 關係中也無法一致地解析它們)、在 href 屬性中將 ampersand 以原始字元嵌入而非進行跳脫,以及對同一組頁面同時混用 HTML head 標籤與 HTTP 標頭或 XML sitemap 的 hreflang 條目。每個群組請挑選一種實作方式;否則飄移勢在必行。
為你的網站挑選正確的方式
如果你的頁面是由模板化來源建置、語系資料已存放在版本庫中,那麼命令列腳本或靜態網站產生器模板就是正確的起點:它保留了使互相回指連結得以強制執行的「單一真實來源」。如果你的頁面由 CMS 管理、人工編輯,或你只是在回應一次性需求,那麼 Lizely 的瀏覽器端 Hreflang 產生器就能在不撰寫或維護任何程式碼的情況下,提供相同的跳脫、大小寫與 x-default 規則。無論哪一種,最重要的紀律完全相同:群組中的每個頁面都必須發布相同的集合(包括其自身),並且在宣告工作完成之前,必須對線上頁面進行爬取,以確認互相回指的連結與 200 回應。
如果你正在權衡各種選項,htaccess 轉 Nginx:命令列 vs 線上比較對此有詳細說明。