1. 我看到用一張圖做成的價目表,為什麼會皺眉

做 SEO 二十多年,有一種畫面我特別敏感:一個很漂亮的價目表、營業時間表或課程表,設計精美,但整塊其實是一張圖片。對使用者來說看起來沒問題,對搜尋引擎來說卻幾乎是一片空白。因為搜尋引擎讀的是原始碼裡的文字,不是圖片裡畫的字。這一整塊資訊,在它眼中等於不存在。

語意化 HTML 的核心精神很單純:用對的標籤裝對的內容。表格資料用表格標籤、並列清單用清單標籤、名詞配說明用定義清單。這麼做不只是為了規矩好看,而是讓機器能正確理解每一塊內容的角色。這篇我從「原始碼可見文字原則」談起,帶到價目表、時間表、列表各自該用什麼標籤,以及營業時間如何搭配結構化資料。

1.1 搜尋引擎讀不到圖片裡的文字

把文字資訊做成圖片,最大的代價是這些字失去了被搜尋、被理解的能力。使用者無法選取或複製,螢幕閱讀器唸不出來,搜尋引擎也無法把它納入內容評估。一張寫著「單次諮詢 3000 元」的圖片,搜尋引擎頂多從檔名或 alt 猜個大概,無法真正理解這是價格資訊。當競爭對手用可讀文字呈現同樣的內容,而你用圖片,你在相關搜尋的競爭力就先輸了一截。

1.2 原始碼可見文字原則:能用 Ctrl+U 找到才算數

我在驗收時有一條很實用的判準,叫原始碼可見文字原則:用瀏覽器的「檢視原始碼」打開頁面,你希望被搜尋引擎讀到的每一段重要文字,都應該能在原始碼裡搜尋得到。如果一段價格、一份營業時間在原始碼裡找不到,代表它要嘛是圖片,要嘛是靠前端渲染才出現的,兩種情況搜尋引擎都可能讀不到。這裡要特別分清楚「檢視原始碼」和「檢查元素」的差別,它們看到的東西不一樣:

比較項目 檢視原始碼(Ctrl+U) 檢查元素(F12)
顯示內容 伺服器回傳的原始 HTML 瀏覽器渲染後的最終 DOM
時間點 網頁載入第一瞬間 JavaScript 執行後的結構
看得到 JS 生成的內容嗎 看不到 看得到
對應誰的視角 搜尋引擎最先讀到的版本 使用者最終看到的畫面

我的實務習慣是,驗收任何一塊重要的表格或清單時,都用檢視原始碼而不是檢查元素。因為檢查元素會顯示 JavaScript 執行後的結果,那是使用者看到的,不代表搜尋引擎讀得到。只有在檢視原始碼裡找得到的文字,才是真正安全的。

2. 價目表、時間表、列表:各自有對的標籤

語意化不是一句口號,它要落在具體的標籤選擇上。不同結構的資訊,對應不同的 HTML 標籤,選對了搜尋引擎才理解得到位。

2.1 有列有欄的二維資料,用 table

價目表、營業時間、規格比較這類「有行有列」的二維資料,天生就該用 <table>。表格標籤本身就在告訴搜尋引擎:這是結構化的對照資料,這一格對應這個欄位、這一列。用對的表頭標記,語意會更完整:

<table>
  <thead>
    <tr><th>服務項目</th><th>時長</th><th>費用</th></tr>
  </thead>
  <tbody>
    <tr><td>初次諮詢</td><td>60 分鐘</td><td>3000 元</td></tr>
    <tr><td>方案檢視</td><td>90 分鐘</td><td>4500 元</td></tr>
  </tbody>
</table>

<th> 標出表頭,而不是全部用 <td>,是我常提醒的細節。表頭標記讓搜尋引擎與螢幕閱讀器知道每一欄的意義,理解就更準確。

2.2 並列的項目,用 ul 或 ol

沒有欄位對照關係、只是一組並列項目的內容,例如服務特色、注意事項,用 <ul>(無序)或 <ol>(有序)才對。我看過不少頁面把清單用一堆 <div> 加前面一個圖示做出來,視覺上像清單,語意上卻只是散落的區塊:

<!-- 正確:語意清單 -->
<ul>
  <li>提供完整網站結構稽核</li>
  <li>附可對照的驗收清單</li>
  <li>交付後提供複驗</li>
</ul>

<li> 包每一項,搜尋引擎就知道這是一組同層級的並列項目;若步驟有先後順序,例如流程步驟,就改用 <ol> 讓順序也帶有語意。

2.3 名詞配說明的成對資料,用 dl

還有一種常見結構是「名詞加解釋」的成對資料,例如常見問題的簡答、規格的項目與數值。這種用定義清單 <dl> 最貼切,<dt> 放名詞、<dd> 放對應說明:

<dl>
  <dt>交付時間</dt>
  <dd>完整稽核約需五個工作天</dd>
  <dt>交付形式</dt>
  <dd>含問題清單與優先順序建議</dd>
</dl>

<dl> 而不是用兩欄表格硬湊,是因為這種資料的本質是「一個詞對一段說明」的配對,不是二維對照。標籤選得貼近資料的本質,語意就越精準。這是我判斷一個前端有沒有 SEO 意識的細節之一。

3. 圖片與 CSS icon 都不該取代文字

前面談了該用什麼標籤,這裡談反面:哪些做法會把文字資訊藏起來,讓搜尋引擎讀不到。最常見的兩種就是用圖片承載文字,以及用 CSS 圖示取代文字。

3.1 圖片不該是主要資訊的唯一載體

圖片當然可以用,問題出在當它變成「主要資訊的唯一載體」。裝飾性的插圖、示意圖都沒問題,但如果一頁的價格、營業時間、聯絡方式、規格這些關鍵資訊只存在於圖片裡,搜尋引擎就讀不到。我的原則是:圖片可以輔助呈現,但同樣的資訊一定要有一份可讀的文字版本存在於原始碼裡。若真的需要用圖片呈現複雜圖表,至少要用 alt 描述重點,並在鄰近提供文字說明。

3.2 CSS icon 取代文字的陷阱

另一個容易忽略的是用 CSS 或圖示字型畫出來的圖示。有些設計會用一個圖示代表「營業中」、用星星圖示代表評分,但這些圖示背後如果沒有對應的文字,搜尋引擎和螢幕閱讀器都無從得知它的意思。它們看到的可能只是一個空的 <i><span>。解法是讓圖示只負責視覺,語意交給文字:圖示旁邊放上實際文字,或用適當的替代文字標註。圖示是給眼睛看的裝飾,不該是承載意義的唯一方式。

4. 營業時間:用文字呈現,再搭配 OpeningHoursSpecification

營業時間是我最常看到被做成圖片的資訊,對有實體門市的網站來說,這一塊做對做錯,直接影響本地搜尋的表現。正確做法分兩層:畫面上用可讀文字或表格呈現,原始碼裡再補上結構化資料。

4.1 先用 table 把營業時間變成可讀文字

第一層是讓營業時間成為原始碼裡找得到的文字。用表格呈現星期與時段是最清楚的:

<table>
  <thead>
    <tr><th>星期</th><th>營業時間</th></tr>
  </thead>
  <tbody>
    <tr><td>週一至週五</td><td>09:00–18:00</td></tr>
    <tr><td>週六</td><td>10:00–16:00</td></tr>
    <tr><td>週日</td><td>休息</td></tr>
  </tbody>
</table>

這一步就滿足了原始碼可見文字原則:任何人用檢視原始碼都能找到營業時間的實際文字,搜尋引擎也讀得到。而且這份內容必須在初始 HTML 就存在,不能是靠前端渲染才出現的,否則搜尋引擎第一時間仍讀不到。

4.2 再用 OpeningHoursSpecification 補上機器可讀的語意

第二層是結構化資料。可讀文字讓搜尋引擎「看得到」營業時間,結構化資料則讓它「精確理解」哪天幾點到幾點營業。這一塊我建議用 LocalBusiness 搭配 OpeningHoursSpecification:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "LocalBusiness",
  "name": "範例店家",
  "url": "https://example.com/store/",
  "openingHoursSpecification": [
    {
      "@type": "OpeningHoursSpecification",
      "dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
      "opens": "09:00",
      "closes": "18:00"
    },
    {
      "@type": "OpeningHoursSpecification",
      "dayOfWeek": "Saturday",
      "opens": "10:00",
      "closes": "16:00"
    }
  ]
}
</script>

這裡有個我一定會檢查的重點:結構化資料裡寫的時間,必須和畫面上文字呈現的時間完全一致。標記寫週六到十六點、頁面卻寫到十七點,這種不一致會讓搜尋引擎不信任你的標記。結構化資料是對畫面內容的補強,不是另一份各說各話的資料。

4.3 多分店要有各自獨立的 URL 與結構化資料

如果有多間門市,常見的錯誤是把所有分店擠在同一頁,只用前端切換顯示。這樣每間店就沒有自己可被索引的入口。正確做法是每間分店一個獨立 URL,各自帶上該店的營業時間文字與對應的 LocalBusiness 結構化資料。這樣搜尋引擎才能把每間店當成獨立的在地實體來理解與呈現。我在稽核連鎖型網站時,這一點幾乎是必查項,因為它直接關係到每間分店能不能在自己的商圈被搜尋到。

5. 常見問題

5.1 用圖片做的表格加上 alt 就沒問題了嗎

alt 有幫助,但無法完全彌補。alt 適合描述一張圖的重點,可是一份有多列多欄的價目表,資訊量遠超過一句 alt 能承載的。搜尋引擎拿到的仍然只是一句概述,而不是每一格的實際資料,使用者也無法選取複製。我的建議是,複雜的表格資料一律用真正的 <table> 呈現,把 alt 留給真正是圖的內容。用 alt 去補救圖片表格,是治標不治本。

5.2 為了設計美觀,價目表非得用圖片不可怎麼辦

設計美觀和語意化並不衝突。<table> 的外觀完全可以用 CSS 調成任何想要的樣式,圓角、配色、字體都能做到,而且還保有可讀、可選取、可被搜尋的優點。真正該避免的是為了省事而直接把設計稿切成一張圖貼上去。我的做法一向是先用正確的語意標籤把結構做出來,再用樣式追求視覺效果,兩者可以兼得,不必二選一。

5.3 我的表格在檢查元素裡看得到,是不是就沒問題

不一定。檢查元素顯示的是 JavaScript 執行後的最終畫面,那是使用者看到的版本,不代表搜尋引擎讀得到。正確的驗收方式是用檢視原始碼,也就是 Ctrl+U,去確認表格內容存在於伺服器回傳的初始 HTML 裡。如果檢查元素看得到、檢視原始碼卻找不到,代表這塊是前端渲染的,搜尋引擎第一時間讀不到,需要改用伺服器端渲染或預先渲染來解決。

5.4 清單一定要用 ul,用 div 加圖示做成清單的樣子不行嗎

視覺上做得像清單,不等於語意上是清單。一堆 <div> 在搜尋引擎眼中只是一群沒有從屬關係的區塊,它讀不出「這是一組並列項目」這件事。用 <ul><li>,語意就明確了,而外觀一樣可以用 CSS 自訂項目符號或改成任何樣式。花一樣的力氣,用對標籤能多換到語意的正確,沒有理由不用。

5.5 營業時間只放結構化資料、畫面不顯示文字可以嗎

不建議。結構化資料是對畫面內容的補強,不是替代品。如果畫面上完全沒有可見的營業時間文字,只有藏在原始碼裡的 JSON-LD,不僅使用者看不到,搜尋引擎也可能因為找不到對應的可見內容而不採信這份標記。正確順序是先讓營業時間以可讀文字或表格呈現在畫面上,再用結構化資料補強,兩者內容一致,這樣才完整。

5.6 定義清單 dl 和用表格做兩欄有什麼差別

差別在資料的本質。<dl> 表達的是「一個名詞對應一段說明」的配對關係,例如問題與簡答、項目與描述;<table> 表達的是「有行有列、可交叉對照」的二維資料,例如不同方案在不同欄位下的值。如果你的資料只是成對的名詞與解釋,用 <dl> 語意更貼切;如果需要跨欄跨列對照,才用 <table>。選標籤的依據始終是內容的結構,而不是它看起來像什麼。