跳至內容

為 VitePress 部落格加上 Waline 留言系統

建好站台之後就在想要不要幫部落格加個留言功能。 畢竟技術文章有時候真的需要跟讀者討論交流,但又不想搞得太複雜。 考慮過幾個方案後,最後選擇了 Waline,這篇文章就來聊聊為什麼選它,以及實際整合的過程。


為什麼選 Waline?

在決定用哪套留言系統之前,我其實看了好幾個選項:

Disqus 雖然老牌但太重了,而且免費版有廣告,隱私問題也讓人不太放心。

Giscus 基於 GitHub Discussions,對技術部落格來說很棒,但問題是不是每個讀者都有 GitHub 帳號。 我自己寫的內容有技術文也有生活隨筆,如果只有 GitHub 用戶能留言,感覺門檻有點高。

最後選了 Waline 的原因很簡單:

  1. 支援匿名留言:讀者可以直接留言,不用強制登入
  2. 多種登入方式:想登入的話也支援 GitHub、Google 等社群帳號
  3. 繁體中文支援完善:介面和提示都有繁中翻譯
  4. 無後端煩惱:後端可以部署在 Vercel,跟部落格用同一個平台
  5. 功能完整:表情、Markdown、圖片上傳、郵件通知都有

實作步驟

第一步:安裝 Waline 用戶端套件

首先要把 Waline 的用戶端套件裝起來。

我的專案用的是 pnpm:

bash
pnpm add @waline/client

如果你用 npm 或 yarn:

bash
npm install @waline/client
# or
yarn add @waline/client

第二步:建立 Waline Vue 元件

接下來要建立一個 Vue 元件來包裝 Waline。

在 VitePress 專案中,我把它放在 docs/.vitepress/theme/components/WalineComment.vue

vue
<script setup lang="ts">
import { onMounted, onUnmounted, watch, ref } from 'vue'
import { useRoute, useData } from 'vitepress'
import { init } from '@waline/client'
import '@waline/client/style'

const route = useRoute()
const { isDark } = useData()
const walineInstance = ref<any>(null)

// Waline 設定
const walineOptions = {
  el: '#waline-comments',
  serverURL: 'https://your-waline-server.vercel.app', // 替換成你的後端 URL
  lang: 'zh-TW',
  locale: {
    placeholder: '留下你的想法...',
    // ... 其他繁體中文翻譯
  },
  dark: isDark.value ? 'html.dark' : 'auto',
  meta: ['nick', 'mail', 'link'],
  requiredMeta: ['nick'],
  pageSize: 10,
  emoji: [
    'https://cdn.jsdelivr.net/npm/@waline/emojis@1.2.0/weibo',
    'https://cdn.jsdelivr.net/npm/@waline/emojis@1.2.0/bilibili',
  ],
  pageview: true,
  comment: true,
}

// 初始化
onMounted(() => {
  walineInstance.value = init(walineOptions)
})

// 路由變化時更新留言
watch(() => route.path, () => {
  if (walineInstance.value) {
    walineInstance.value.update()
  }
})

// 深色模式切換
watch(isDark, (newValue) => {
  if (walineInstance.value) {
    walineInstance.value.update({
      dark: newValue ? 'html.dark' : 'auto',
    })
  }
})

// 清理
onUnmounted(() => {
  if (walineInstance.value) {
    walineInstance.value.destroy()
  }
})
</script>

<template>
  <div class="waline-wrapper">
    <div class="waline-container">
      <h2 class="waline-title">💬 留言討論</h2>
      <div id="waline-comments"></div>
    </div>
  </div>
</template>

這個元件做了幾件重要的事:

  1. 繁體中文設定lang: 'zh-TW'locale 物件確保介面是繁中
  2. 深色模式支援:監聽 VitePress 的 isDark 狀態,自動切換佈景主題
  3. 路由更新:當切換文章時,留言區會自動更新
  4. 生命週期管理:確保元件銷毀時清理 Waline 實體

第三步:註冊元件

docs/.vitepress/theme/index.ts 中註冊這個元件:

typescript
import WalineComment from './components/WalineComment.vue'

export default {
  extends: DefaultTheme,
  Layout,
  enhanceApp({ app }) {
    app.component('WalineComment', WalineComment)
    // ... 其他元件
  }
}

第四步:整合到文章頁面

接著要在文章頁面底部加上留言區。

我的做法是修改 docs/.vitepress/theme/Layout.vue,使用 VitePress 的 #doc-after slot:

vue
<template>
  <Layout>
    <template #doc-after>
      <!-- 只在文章頁面顯示留言 -->
      <WalineComment v-if="isArticlePage && frontmatter.comment !== false" />
    </template>
  </Layout>
</template>

<script setup lang="ts">
import { useData } from 'vitepress'
import { computed } from 'vue'

const { page, frontmatter } = useData()

// 判斷是否為文章頁面
const isArticlePage = computed(() => {
  return (frontmatter.value.date || frontmatter.value.tags) &&
         !page.value.relativePath.includes('index.md')
})
</script>

這樣做的好處是:

  • 留言區只會出現在正式的文章頁面
  • 首頁、分類頁這些列表頁不會有留言區
  • 如果某篇文章不想開放留言,只要在 frontmatter 加上 comment: false 就行

第五步:樣式調整

Waline 預設的樣式可能跟你的主題不太搭,可以加一些 CSS 調整:

css
.waline-wrapper {
  margin-top: 4rem;
  padding-top: 2rem;
  border-top: 1px solid var(--vp-c-divider);
}

/* 深色模式適配 */
html.dark .wl-card {
  background-color: var(--vp-c-bg-soft);
  color: var(--vp-c-text-1);
}

html.dark .wl-editor {
  background-color: var(--vp-c-bg-soft);
  border-color: var(--vp-c-divider);
}

/* 按鈕樣式與主題一致 */
.wl-btn {
  background-color: var(--vp-c-brand-1) !important;
}

.wl-btn:hover {
  background-color: var(--vp-c-brand-2) !important;
}

/* 統一操作按鈕顏色 */
.wl-login-btn,
.wl-logout-btn,
.wl-refresh {
  color: var(--vp-c-brand-1) !important;
}

後端部署(Vercel)

前端整合完成後,還需要部署 Waline 的後端服務。

最簡單的方式是用 Vercel:

1. 一鍵部署到 Vercel

點擊以下連結直接部署(會自動建立正確的範本倉庫):

https://vercel.com/new/clone?repository-url=https://github.com/walinejs/waline/tree/main/example

重要提醒:不要直接 fork Waline 主專案再部署,那樣會因為包含完整原始碼而導致構建失敗。使用上面的一鍵部署連結,Vercel 會自動從 example 目錄建立正確的範本。

2. 註冊 LeanCloud 資料庫

LeanCloud 國際版 註冊並建立應用,取得以下憑證:

  1. 進入應用設定 → 應用憑證
  2. 複製以下資訊:
    • App ID
    • App Key
    • Master Key
    • 伺服器地址(REST API) - 這就是 LEAN_SERVER

3. 設定 Vercel 環境變數

回到 Vercel 專案,進入 Settings → Environment Variables,新增:

LEAN_ID=your_leancloud_app_id
LEAN_KEY=your_leancloud_app_key
LEAN_MASTER_KEY=your_leancloud_master_key
LEAN_SERVER=https://your_app_id.api.lncldglobal.com

說明

  • 前三個從 LeanCloud 「應用憑證」複製
  • LEAN_SERVER 是 LeanCloud 的 API 端點地址,通常格式為 https://前8碼.api.lncldglobal.com
  • 也可以使用其他資料庫方案(PostgreSQL、MongoDB 等),詳見 Waline 文件

設定完成後,點擊 Redeploy 重新部署。

4. 更新前端設定

部署完成後,你會得到一個 Vercel URL(例如:https://your-waline.vercel.app),把它填到前面元件的 serverURL 設定裡就完成了。


踩過的坑

整合過程中遇到幾個小問題,記錄一下:

1. 深色模式不會自動切換

一開始發現切換深色模式時,Waline 的主題沒有跟著變。後來發現要用 watch 監聽 isDark 變化,然後呼叫 update() 方法更新主題。

2. 路由切換時留言沒更新

在 VitePress 中切換文章時是用戶端路由,Waline 不會自動更新。需要監聽 route.path 變化,手動觸發 update()

3. 別忘了清理實體

如果沒有在 onUnmounted 清理 Waline 實體,會有記憶體洩漏的風險,特別是在頻繁切換頁面時。


進階功能

Waline 還有一些好用的進階功能:

郵件通知 (如果有 SMTP)

可以設定當有新留言或被回覆時,自動發送郵件通知。

在 Vercel 環境變數中設定:

SMTP_SERVICE=Gmail
SMTP_USER=your_email@gmail.com
SMTP_PASS=your_app_password
SITE_NAME=你的部落格名稱
SITE_URL=https://yourblog.com

整合 Akismet 反垃圾留言

整合 Akismet 來過濾垃圾留言:

AKISMET_KEY=your_akismet_key

管理後台

瀏覽 https://your-waline-server.vercel.app/ui 可以進入管理後台,審核和管理留言。


總結

整合 Waline 到 VitePress 其實沒想像中複雜,主要就是:

  1. 安裝套件
  2. 建立 Vue 元件
  3. 處理深色模式和路由更新
  4. 部署後端服務
  5. 調整樣式

使用 Waline 後,留言系統變得輕量又好用,對技術部落格來說非常合適。 部署在 Vercel 也省去了維護後端的麻煩,跟部落格用同一個平台,感覺很一致。 包括你正在看的這篇文章底下的留言區,就是用 Waline 實作的!

如果你也在找適合技術部落格的留言系統,Waline 真的是個不錯的選擇。

💬 留言討論

Released under the MIT License.