【Hexo】Icarus 主題串接 GA 顯示網站 PV 和 UV

【Hexo】Icarus 主題串接 GA 顯示網站 PV 和 UV

本篇重點

  • 如何修改 Icarus 主題、新增自訂插件
  • Icarus 主題串接 Google Analytics 顯示網站 PV 和 UV
  • 使用 forgetfulengineer/google-analytics-data-api-netlify API 顯示網站數據
  • 使用 countUp.js 建立數字的動畫效果

Icarus 主題原生僅支援不蒜子提供的 PV 計數功能,無法串接 Google Analytics 的瀏覽數據。為了顯示來自 GA 的數據,我建立了 forgetfulengineer/google-analytics-data-api-netlify 作為 API 接口,並修改 Icarus 主題,使其能透過該 API 顯示網站數據。本文紀錄如何修改 Icarus 主題、新增自訂插件,並串接 API 顯示網站的 PV 與 UV。

1. 架設網站 PV/UV 查詢 API

fork forgetfulengineer/google-analytics-data-api-netlify 並部署到 Netlify,建立一個可查詢網站 PV/UV 的 API,詳細部屬流程請查看【API】使用 Google Analytics Data API 架設網站 PV/UV 查詢 API

2. 添加自定義插件

Icarus 主題大部分的通用布局文件已被移至 repository hexo-component-inferno,因此可以在不影響主題更新的情況下添加自定義插件,布局文件的詳細說明可查看官方文件

建立 ga_count 的 schema

新增 icarus 設定檔參數的概要

檔案路徑
1
2
3
4
5
6
7
8
themes/
├── icarus/
│ └── include/
│ └── schema/
│ └── plugin/
│ └── ga_count.json ← 新增參數概要
│ └── common/
│ └── plugins.json ← 增加引用路徑

新增參數概要

/include/schema/plugin/ga_count.json查看
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "/plugin/ga_count.json",
"description": "Ga count plugin",
"type": "object",
"properties": {
"ga_count_api": {
"type": "string",
"description": "Google Analytics Data API",
"nullable": true,
"examples": ["https://ga-api-demo.netlify.app/.netlify/functions/pageview"]
}
},
"required": ["ga_count_api"]
}

增加引用路徑

/include/schema/common/plugins.json查看
1
2
3
4
5
{
...
"ga_count": "plugin/ga_count.json"
...
}

主題設定啟用 ga_count

填入 API 路徑 https://{your-site}.netlify.app/.netlify/functions/pageview

_config.icarus.yml
1
2
3
plugins:
ga_count:
ga_count_api: https://ga-api-demo.netlify.app/.netlify/functions/pageview

建立 ga_count 元件

新增 /layout/plugin/ga_count.jsx 作為 Hexo 客製化插件,用於前端呼叫 GA API,並顯示瀏覽數。

jsx查看
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
const { Component, Fragment } = require('inferno');
const { cacheComponent } = require('hexo-component-inferno/lib/util/cache');

class GACount extends Component {
render() {
const { head, apiUrl, countUpJs } = this.props;

if (head) return null;

let gaCountJs = `(function() {
var url = "${apiUrl}";
fetch(url, { method: 'get' })
.then(response => response.json())
.then(json => {
var sitePvElement = document.getElementById('ga_value_site_pv');
var siteUvElement = document.getElementById('ga_value_site_uv');
var pvElement = document.getElementById('ga_value_page_pv');

if (sitePvElement) {
const sitePv = new countUp.CountUp('ga_value_site_pv', json.pv, { enableScrollSpy: true, scrollSpyOnce: true });
}
if (siteUvElement) {
const siteUv = new countUp.CountUp('ga_value_site_uv', json.uv, { enableScrollSpy: true, scrollSpyOnce: true });
}
if (pvElement) {
const pagePv = new countUp.CountUp('ga_value_page_pv', json.pageViews, { enableScrollSpy: true, scrollSpyOnce: true });
}
});
})();`;

return <Fragment>
<script src={countUpJs}></script>
<script dangerouslySetInnerHTML={{ __html: gaCountJs }}></script>
</Fragment>;
}
}

GACount.Cacheable = cacheComponent(GACount, 'plugin.ga_count', props => {
const { helper, head, page, plugin } = props;
const { url_for, cdn } = helper;
const apiBase = plugin.ga_count_api;
const path = url_for(page.path);
const apiUrl = (page.layout == 'post') ? `${apiBase}?path=${encodeURIComponent(path)}` : apiBase;
const countUpJs = cdn('countup.js', '2.9.0', 'dist/countUp.umd.js');

return { head, apiUrl, countUpJs };
});

module.exports = GACount;
  • 元件快取機制 cacheComponent
    Icarus 主題特有的效能優化方式,避免不必要的重複渲染,每個組件會依據頁面內容快取。

  • API 路徑動態處理:
    根據是否為文章頁,決定是否要加上 ?path= 查詢字串,讓後端能計算單篇 PV。

    jsx查看
    1
    const apiUrl = (page.layout == 'post') ? `${apiBase}?path=${encodeURIComponent(path)}` : apiBase;
  • 使用 countUp.js 呈現數字動畫:
    利用 cdn() 方法載入 CountUp 套件,再透過 enableScrollSpyscrollSpyOnce 讓動畫僅在畫面滾動到數字時觸發一次。

    jsx查看
    1
    new countUp.CountUp('ga_value_page_pv', json.pageViews, { enableScrollSpy: true, scrollSpyOnce: true })
  • plugin 載入處理:
    Icarus 主題中,layout/common/head.jsxlayout/common/scripts.jsx 都會載入 plugin,但 ga_count.jsx 只需載入一次,且必須等頁面元素渲染完成後再執行,避免抓不到 PV 元素,因此加入判斷,當處於 <head> 區塊時跳過渲染:

    jsx查看
    1
    if (head) return null;
  • 資料顯示區塊:
    透過 getElementById 取得 3 個容器(site PV、site UV、單篇 PV),再用 CountUp 顯示動畫數字。

3. 修改模板:顯示 PV / UV 數字

修改 article.jsx(文章 PV)

在文章內容(layout/common/article.jsx)中新增顯示該篇文章瀏覽數(PV)的區塊。

jsx查看
1
2
3
4
5
6
7
8
{/* Visitor counter */}
{!index && plugins && plugins.ga_count.ga_count_api ? (
<span class="level-item" id="ga_container_page_pv">
<i class="far fa-eye"></i>
<span id="ga_value_page_pv">-</span>
</span>
) : null;
}

修改 article.jsx

在頁尾(layout/common/footer.jsx)中新增顯示全站瀏覽及訪客數(PV/UV)的區塊。

取得 ga_count 設定並修改瀏覽數據顯示內容

jsx查看
1
2
3
4
- showVisitorCounter: plugins && plugins.busuanzi === true,
- visitorCounterTitle: _p('plugin.visitor_count', '<span id="busuanzi_value_site_uv">0</span>')
+ showVisitorCounter: plugins && plugins.ga_count.ga_count_api,
+ visitorCounterTitle: '<i class="far fa-eye"></i>總瀏覽數:<span id="ga_value_site_pv">-</span>&nbsp;&nbsp;<i class="fa-solid fa-person-walking"></i>總訪客數:<span id="ga_value_site_uv">-</span>'

將原本 Busuanzi 使用的容器 ID 改為 GA 專用的容器

jsx查看
1
2
- {showVisitorCounter ? <span id="busuanzi_container_site_uv"
+ {showVisitorCounter ? <span id="ga_container_site_pvuv"

修改 footer.jsx

結論

透過修改 Icarus 主題與串接 forgetfulengineer/google-analytics-data-api-netlify,就能在網站中顯示來自 Google Analytics 的瀏覽數據,即時掌握網站的流量情況。

如果在串接過程中遇到問題,或有任何建議,歡迎留言讓我知道~接下來計畫新增「熱門文章排行榜」,敬請期待!

延伸閱讀

【Hexo】Icarus 主題串接 GA 顯示網站 PV 和 UV

https://forgetfulengineer.github.io/Other/Hexo/Icarus-Integrate-Ga-Api-Pv-Uv/

作者

健忘工程師

發表於

2025-07-30

更新於

2025-07-30

許可協議


你可能也想看

【API】使用 Google Analytics Data API 架設網站 PV/UV 查詢 API
【jQuery、css】解析六種組合選擇器:相連、+、~、>、,、空格
【jQuery】click 事件綁定方式比較

評論

複製完成