PageSpeed Insights APIを利用して、新規作成したページのスコアをチェックする
Table of contents
- 記事内で使用している主なソフトウェアのバージョン
- 前提条件
- 概要
- Page Speed Insightsとは
- 1. Newtのコンテンツ情報をもとに、調査対象のURLを特定する
- 1-1. Newtのコンテンツ情報をもとに、1週間以内に更新されたコンテンツを特定する
- 1-2. 更新されたコンテンツ情報をもとに、更新があったページのURLを特定する
- 2. PageSpeed Insights APIを利用して、スコアを取得する
- 2-1. PageSpeed Insights APIで利用するAPIキーを作成する
- 2-2. PageSpeed Insights APIでパフォーマンススコアを取得する
- 2-3. すべてのスコアを取得する
- 3. Slack Appを作成する
- 3-1. Slack Appを作成する
- 3-2. パーミッションを設定する
- 3-3. ワークスペースにインストールする
- 3-4. チャンネルにAppを追加する
- 3-5. チャンネルのIDを確認する
- 4. 取得したスコアをSlackに通知する
- 4-1. node-slack-sdkをインストールする
- 4-2. Slackに通知する
- 4-3. メッセージを装飾する
- 5. GitHub Actionsを設定する
- 5-1. シークレットの定義
- 5-2. ワークフローの定義
このチュートリアルでは、PageSpeed Insights API を利用して、新規作成・更新したページのスコアをチェックする方法を紹介します。処理の実行にはGitHub Actionsを利用します。
記事内で使用している主なソフトウェアのバージョン
- newt-client-js(
newt-client-js
): 3.2.7 - node-slack-sdk(
@slack/web-api
): 6.9.1
前提条件
- GitHub Actionsを実行できる環境があること
- メッセージを通知するSlackのワークスペース、チャンネルを用意していること
GitHub Actionsについて、詳細を知りたい場合はGitHubの GitHub Actions のドキュメントをご確認ください。
Slackの基本的な使い方について知りたい場合は、Slackの はじめ方 のドキュメントをご確認ください。
概要
定期的にPageSpeed Insights APIを利用してスコアを取得し、Slackに通知を行います。
ここでは、以下のように処理を行います。
- GitHub Actions を利用して、週に1回、処理をトリガーする
- Newtのコンテンツ情報をもとに、1週間以内に更新されたコンテンツを特定する
- 更新されたコンテンツ情報をもとに、更新があったページのURLを特定する
- 更新があったページについて、PageSpeed Insights APIを利用して、スコアを取得する
- 取得したスコアを Slack に通知する
最終的に、Slackに以下のような通知を送ります。
「モバイル」と「デスクトップ」の両方について、「パフォーマンス」「ユーザー補助」「おすすめの方法」「SEO」の4つのスコアを表示しています。
Page Speed Insightsとは
Googleが提供する PageSpeed Insights を利用すると、ウェブページのパフォーマンス・アクセシビリティ・SEO等のスコアを測定できます。
これらの項目を定期的に確認・改善することで、ユーザー体験を向上させることができます。また、ページエクスペリエンスは、検索結果にも影響する ため、そういった意味でも継続的に確認・改善することは、価値のある取り組みと考えられます。
1. Newtのコンテンツ情報をもとに、調査対象のURLを特定する
まず、GitHub Actionsで実行するjsファイルを作成して、以下の処理を実行できるようにしましょう。
- Newtのコンテンツ情報をもとに、1週間以内に更新されたコンテンツを特定する
- 更新されたコンテンツ情報をもとに、更新があったページのURLを特定する
順に説明していきます。
1-1. Newtのコンテンツ情報をもとに、1週間以内に更新されたコンテンツを特定する
Newtで管理しているコンテンツのうち、1週間以内に更新されたコンテンツを特定しましょう。.github/actions/psi.js
を作成します。ここでは、以下のことを行っています。
client
を作成し、NewtのCDN APIを利用するためのクライアントを作成するgetArticles
でNewtで管理しているコンテンツの一覧を取得する(appUid
とmodelUid
にはご利用されているApp・モデルのUIDを指定してください)。また、このモデルではtitle
にコンテンツのタイトル、slug
にコンテンツのスラッグが登録されているものとしますoneWeekBefore
に実行時から1週間前の日時を指定し、最終更新の日時が1週間前より大きい投稿を取得する
※ Newt CDN APIでは、_sys.updated
に最終更新の日時(最後に保存して公開した日時。保存しても公開しなかった場合は更新されない)が入るため、この日時が oneWeekBefore
より大きい投稿を取得しています。gt
演算子を利用することで「より大きい」もののみを取得できます。詳細はNewtの API Reference をご確認ください。
1require('dotenv').config()
2const { createClient } = require('newt-client-js')
3
4const client = createClient({
5 spaceUid: process.env.NEWT_SPACE_UID + '',
6 token: process.env.NEWT_CDN_API_TOKEN + '',
7 apiType: 'cdn',
8})
9
10const getArticles = async (query) => {
11 const { items } = await client.getContents({
12 appUid: 'blog',
13 modelUid: 'article',
14 query,
15 })
16 return items
17}
18
19;(async () => {
20 try {
21 const oneWeekBefore = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
22 const updatedArticles = await getArticles({
23 '_sys.updatedAt': { gt: oneWeekBefore.toISOString() },
24 select: ['title', 'slug'],
25 })
26
27 return
28 } catch (err) {
29 console.error(err)
30 }
31})()
この時点で、環境変数は以下のようになっています。以下を、実際の値で置き換えて定義してください。
1NEWT_SPACE_UID=sample-for-docs
2NEWT_CDN_API_TOKEN=xxxxxxxxxxxxxxx
1-2. 更新されたコンテンツ情報をもとに、更新があったページのURLを特定する
次に、更新があったページのURL(PageSpeed Insights APIでパフォーマンスをチェックしたいURL)を特定します。
ここでは、ブログの記事が更新されると、ブログの記事詳細ページ /articles/[slug]
が更新されるとします。
サイトのドメインが https://hoge.vercel.app
だとすると、取得した article
から、PageSpeed Insights APIでパフォーマンスをチェックしたいURLは https://hoge.vercel.app/articles/${article.slug}
とわかります。
2. PageSpeed Insights APIを利用して、スコアを取得する
2-1. PageSpeed Insights APIで利用するAPIキーを作成する
まず、APIキーの取得と使用 のドキュメントを確認して、PageSpeed Insights APIで利用するAPIキーを作成しましょう。
作成したら、そのAPIキーを環境変数 PSI_API_KEY
として登録します。
以下を、実際の値で置き換えて定義してください。
NEWT_SPACE_UID=sample-for-docs
NEWT_CDN_API_TOKEN=xxxxxxxxxxxxxxx
PSI_API_KEY=xxxxxxx
上記のように定義しておくと、process.env.PSI_API_KEY
として利用できるようになります。
2-2. PageSpeed Insights APIでパフォーマンススコアを取得する
最終的には「パフォーマンス」「ユーザー補助」「おすすめの方法」「SEO」の4つのスコアについて、「モバイル」「デスクトップ」の両方のスコアを取得しますが、まずは「デスクトップ」の「パフォーマンス」スコアから取得してみましょう。
getScores
というメソッドを作成します。以下のことを行っています。
- PageSpeed Insights APIを利用するエンドポイントとして、
psiUrl
を定義する。key
として、先程取得したAPIキーを設定する requestUrl
では、測定対象となるURLをurl
パラメータで設定する- 返却されたデータから
lighthouseResult
の中にあるcategories.performance.score
からパフォーマンススコアを取得する console.log
のところで、取得したパフォーマンススコアを表示する
require('dotenv').config()
const { createClient } = require('newt-client-js')
const client = createClient({
spaceUid: process.env.NEWT_SPACE_UID + '',
token: process.env.NEWT_CDN_API_TOKEN + '',
apiType: 'cdn',
})
const getArticles = async (query) => {
const { items } = await client.getContents({
appUid: 'blog',
modelUid: 'article',
query,
})
return items
}
const psiUrl = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?key=${process.env.PSI_API_KEY}`
const getScores = async (url) => {
const requestUrl = `${psiUrl}&url=${url}`
const res = await fetch(requestUrl)
const data = await res.json()
const { categories } = data.lighthouseResult
const performanceScore = Math.round(categories.performance.score * 100)
console.log(performanceScore)
return
}
;(async () => {
try {
const oneWeekBefore = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
const updatedArticles = await getArticles({
'_sys.updatedAt': { gt: oneWeekBefore.toISOString() },
select: ['title', 'slug'],
})
await getScores('https://www.newt.so/docs/quick-start')
return
} catch (err) {
console.error(err)
}
})()
ここでは試しに getScores
を呼び出しています(引数のurl https://www.newt.so/docs/quick-start
のところには、テストで測定したいお好きなURLを設定してください)。
以下のようにnodeコマンドを実行すると、ログとしてスコア(95
など)が表示されるのを確認できます。
node .github/actions/psi
2-3. すべてのスコアを取得する
まず「パフォーマンス」「ユーザー補助」「おすすめの方法」「SEO」の4つのスコアを取得できるようにしましょう。
リクエストの パラメータ として category
を設定することで、「パフォーマンス」以外のスコアも取得できます。
クエリパラメータに以下を追加することで、「パフォーマンス」「ユーザー補助」「おすすめの方法」「SEO」の4つのスコアを取得できます。
&category=performance&category=accessibility&category=best-practices&category=seo
またデフォルトでは、デスクトップのスコアのみが返却されるため、モバイル用のスコアを取得する場合は、別のリクエストを作成し strategy
パラメータに mobile
を設定します。
これらを踏まえて、以下のように修正します。以下のことを行っています。
- psiUrlのクエリパラメータに
category
を複数設定して、「パフォーマンス」「ユーザー補助」「おすすめの方法」「SEO」の4つのスコアを取得する - モバイル用のリクエストとパソコン用のリクエストを別々に作成する(
requestUrlForMobile
とrequestUrlForDesktop
) - 返却されたデータから
lighthouseResult
の中にあるcategories
からそれぞれのスコアを取得する console.log
のところで、取得したパフォーマンススコアを表示する
1require('dotenv').config()
2const { createClient } = require('newt-client-js')
3
4const client = createClient({
5 spaceUid: process.env.NEWT_SPACE_UID + '',
6 token: process.env.NEWT_CDN_API_TOKEN + '',
7 apiType: 'cdn',
8})
9
10const getArticles = async (query) => {
11 const { items } = await client.getContents({
12 appUid: 'blog',
13 modelUid: 'article',
14 query,
15 })
16 return items
17}
18
19const psiUrl = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?key=${process.env.PSI_API_KEY}&category=performance&category=accessibility&category=best-practices&category=seo`
20
21const getScores = async (url) => {
22 const requestUrl = `${psiUrl}&url=${url}`
23 const requestUrlForMobile = `${requestUrl}&strategy=mobile`
24 const requestUrlForDesktop = `${requestUrl}&strategy=desktop`
25
26 const resMobile = await fetch(requestUrlForMobile)
27 const dataMobile = await resMobile.json()
28 const { categories } = dataMobile.lighthouseResult
29 const performanceScoreMobile = Math.round(categories.performance.score * 100)
30 const accessibilityScoreMobile = Math.round(
31 categories.accessibility.score * 100,
32 )
33 const bestPracticesScoreMobile = Math.round(
34 categories['best-practices'].score * 100,
35 )
36 const seoScoreMobile = Math.round(categories.seo.score * 100)
37
38 const resDesktop = await fetch(requestUrlForDesktop)
39 const dataDesktop = await resDesktop.json()
40 const { categories: categoriesDesktop } = dataDesktop.lighthouseResult
41 const performanceScoreDesktop = Math.round(
42 categoriesDesktop.performance.score * 100,
43 )
44 const accessibilityScoreDesktop = Math.round(
45 categoriesDesktop.accessibility.score * 100,
46 )
47 const bestPracticesScoreDesktop = Math.round(
48 categoriesDesktop['best-practices'].score * 100,
49 )
50 const seoScoreDesktop = Math.round(categoriesDesktop.seo.score * 100)
51
52 console.log(
53 performanceScoreMobile,
54 accessibilityScoreMobile,
55 bestPracticesScoreMobile,
56 seoScoreMobile,
57 )
58 console.log(
59 performanceScoreDesktop,
60 accessibilityScoreDesktop,
61 bestPracticesScoreDesktop,
62 seoScoreDesktop,
63 )
64
65 return
66}
67
68;(async () => {
69 try {
70 const oneWeekBefore = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
71 const updatedArticles = await getArticles({
72 '_sys.updatedAt': { gt: oneWeekBefore.toISOString() },
73 select: ['title', 'slug'],
74 })
75 await getScores('https://www.newt.so/docs/quick-start')
76
77 return
78 } catch (err) {
79 console.error(err)
80 }
81})()
試しにローカルサーバーを立ち上げて、curlコマンドを実行してみましょう(getScores
から呼び出すURLはお好きなURLを設定してください)。
node .github/actions/psi
以下のように、スコアが表示されることがわかります。
90 100 100 100
98 100 100 100
上記で利用した以外のリクエスト・レスポンスを利用したい場合は、Googleの ドキュメント をご確認ください。
これで、PageSpeed Insights APIを利用して、スコアを取得できるようになりました。
3. Slack Appを作成する
続いて、取得したスコアをSlackに通知するために、Slack Appを作成します。
3-1. Slack Appを作成する
https://api.slack.com/apps より、Slack Appを作成します。
「Create New App」から「From scratch」を選択して、Appの名前を入力し、メッセージを送るワークスペースを選択します。
入力が終わったら、「Create App」を押します。
3-2. パーミッションを設定する
「OAuth & Permissions」の「Scopes」にある「Bot Token Scopes」から「Add an OAuth Scope」をクリックします。
メッセージを投稿する chat.postMessage では chat:write
のスコープが必要なため、chat:write
を追加しましょう。
3-3. ワークスペースにインストールする
続いて、「OAuth & Permissions」の「OAuth Tokens for Your Workspace」より「Install to Workspace」をクリックして、作成したSlack Appをワークスペースにインストールしましょう。
インストールすると、Bot User OAuth Tokenが発行されます。
このTokenを環境変数 SLACK_APP_TOKEN
として登録しておきましょう。
以下を、実際の値で置き換えて定義してください。
NEWT_SPACE_UID=sample-for-docs
NEWT_CDN_API_TOKEN=xxxxxxxxxxxxxxx
PSI_API_KEY=xxxxxxx
SLACK_APP_TOKEN=xoxb-xxxxxxxxxxxx
3-4. チャンネルにAppを追加する
次にメッセージを通知するチャンネルにAppを追加します。
チャンネル名をクリックし、Integrations
を選択しましょう。「Add apps」より作成したAppを「Add」します。
すると、以下のようにチャンネルに追加されます。
3-5. チャンネルのIDを確認する
続いて、チャンネルIDを確認します。
Slackのアプリを利用している場合、チャンネル名をクリックし、About
を選択しましょう。下部にIDが表示されます。
WebブラウザでSlackを利用している場合、チャンネルのURLを確認します。
SlackのURLは以下のようになっているため、URLからチャンネルIDを確認しましょう。
https://app.slack.com/client/{ワークスペースID}/{チャンネルID}
例えば、URLが https://app.slack.com/client/Txxxxxx/Cyyyyyy
の場合、チャンネルIDは Cyyyyyy
となります。
このチャンネルIDを環境変数 SLACK_CHANNEL_ID
として登録しておきましょう。
以下を、実際の値で置き換えて定義してください。
NEWT_SPACE_UID=sample-for-docs
NEWT_CDN_API_TOKEN=xxxxxxxxxxxxxxx
PSI_API_KEY=xxxxxxx
SLACK_APP_TOKEN=xoxb-xxxxxxxxxxxx
SLACK_CHANNEL_ID=Cyyyyyy
4. 取得したスコアをSlackに通知する
4-1. node-slack-sdkをインストールする
node-slack-sdk をインストールします。
npm install @slack/web-api
# or
yarn add @slack/web-api
4-2. Slackに通知する
続いて、シンプルな形式で、Slackにスコアを送信できるようにしましょう。Slackの chat.postMessage を利用します。
メッセージの作成には blocks を利用します。
以下のことを行っています。
getScores
にtitle
の引数を追加し、タイトル・URL・モバイルの4スコア・デスクトップの4スコアの情報を持たせたblocksを返却できるようにする- 更新したコンテンツがある場合、各コンテンツについて
getScores
を並列に実行する。その際、URLには1-3で特定したURLを指定する(ここではhttps://hoge.vercel.app/articles/${article.slug}
) - chat.postMessage を利用して、メッセージを送る。オプションの詳細については chat.postMessage のドキュメントをご確認ください
1require('dotenv').config()
2const { WebClient } = require('@slack/web-api')
3const { createClient } = require('newt-client-js')
4
5const client = createClient({
6 spaceUid: process.env.NEWT_SPACE_UID + '',
7 token: process.env.NEWT_CDN_API_TOKEN + '',
8 apiType: 'cdn',
9})
10
11const getArticles = async (query) => {
12 const { items } = await client.getContents({
13 appUid: 'blog',
14 modelUid: 'article',
15 query,
16 })
17 return items
18}
19
20const psiUrl = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?key=${process.env.PSI_API_KEY}&category=performance&category=accessibility&category=best-practices&category=seo`
21
22const getScores = async (title, url) => {
23 const requestUrl = `${psiUrl}&url=${url}`
24 const requestUrlForMobile = `${requestUrl}&strategy=mobile`
25 const requestUrlForDesktop = `${requestUrl}&strategy=desktop`
26
27 const resMobile = await fetch(requestUrlForMobile)
28 const dataMobile = await resMobile.json()
29 const { categories } = dataMobile.lighthouseResult
30 const performanceScoreMobile = Math.round(categories.performance.score * 100)
31 const accessibilityScoreMobile = Math.round(
32 categories.accessibility.score * 100,
33 )
34 const bestPracticesScoreMobile = Math.round(
35 categories['best-practices'].score * 100,
36 )
37 const seoScoreMobile = Math.round(categories.seo.score * 100)
38
39 const resDesktop = await fetch(requestUrlForDesktop)
40 const dataDesktop = await resDesktop.json()
41 const { categories: categoriesDesktop } = dataDesktop.lighthouseResult
42 const performanceScoreDesktop = Math.round(
43 categoriesDesktop.performance.score * 100,
44 )
45 const accessibilityScoreDesktop = Math.round(
46 categoriesDesktop.accessibility.score * 100,
47 )
48 const bestPracticesScoreDesktop = Math.round(
49 categoriesDesktop['best-practices'].score * 100,
50 )
51 const seoScoreDesktop = Math.round(categoriesDesktop.seo.score * 100)
52
53 const blocks = [
54 {
55 type: 'section',
56 text: {
57 type: 'mrkdwn',
58 text: `${title}\n${url}\nMobile: ${performanceScoreMobile} ${accessibilityScoreMobile} ${bestPracticesScoreMobile} ${seoScoreMobile}\nDesktop: ${performanceScoreDesktop} ${accessibilityScoreDesktop} ${bestPracticesScoreDesktop} ${seoScoreDesktop}`,
59 },
60 },
61 {
62 type: 'divider',
63 },
64 ]
65
66 return blocks
67}
68
69;(async () => {
70 try {
71 const oneWeekBefore = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
72 const updatedArticles = await getArticles({
73 '_sys.updatedAt': { gt: oneWeekBefore.toISOString() },
74 select: ['title', 'slug'],
75 })
76
77 const blocks = []
78 if (updatedArticles.length === 0) {
79 blocks.push({
80 type: 'section',
81 text: {
82 type: 'mrkdwn',
83 text: `更新されたページはありませんでした`,
84 },
85 })
86 } else {
87 await Promise.all(
88 updatedArticles.map(async (article) => {
89 const _blocks = await getScores(
90 article.title,
91 `https://hoge.vercel.app/articles/${article.slug}`,
92 )
93 blocks.push(..._blocks)
94 }),
95 )
96 }
97
98 const token = process.env.SLACK_APP_TOKEN
99 const client = new WebClient(token)
100 const channel = process.env.SLACK_CHANNEL_ID + ''
101
102 await client.chat.postMessage({
103 channel,
104 blocks,
105 unfurl_links: false,
106 })
107
108 return
109 } catch (err) {
110 console.error(err)
111 }
112})()
これで、シンプルな形式でスコアを通知できるようになりました。
4-3. メッセージを装飾する
最後にメッセージを装飾して、結果を見やすくしてみましょう。
以下を修正します。
scoreWithEmoji
のメソッドを利用して、スコアの隣に絵文字をつけるheader
を追加して、メッセージの先頭にヘッダーを入れる
1require('dotenv').config()
2const { WebClient } = require('@slack/web-api')
3const { createClient } = require('newt-client-js')
4
5const client = createClient({
6 spaceUid: process.env.NEWT_SPACE_UID + '',
7 token: process.env.NEWT_CDN_API_TOKEN + '',
8 apiType: 'cdn',
9})
10
11const getArticles = async (query) => {
12 const { items } = await client.getContents({
13 appUid: 'blog',
14 modelUid: 'article',
15 query,
16 })
17 return items
18}
19
20const psiUrl = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?key=${process.env.PSI_API_KEY}&category=performance&category=accessibility&category=best-practices&category=seo`
21
22const scoreWithEmoji = (score) => {
23 if (score >= 90) {
24 return `:white_check_mark:${score}`
25 } else if (score >= 70) {
26 return `:warning:${score}`
27 } else if (score >= 50) {
28 return `:rotating_light:${score}`
29 } else {
30 return `:scream:${score}`
31 }
32}
33
34const getScores = async (title, url) => {
35 const requestUrl = `${psiUrl}&url=${url}`
36 const requestUrlForMobile = `${requestUrl}&strategy=mobile`
37 const requestUrlForDesktop = `${requestUrl}&strategy=desktop`
38
39 const resMobile = await fetch(requestUrlForMobile)
40 const dataMobile = await resMobile.json()
41 const { categories } = dataMobile.lighthouseResult
42 const performanceScoreMobile = Math.round(categories.performance.score * 100)
43 const accessibilityScoreMobile = Math.round(
44 categories.accessibility.score * 100,
45 )
46 const bestPracticesScoreMobile = Math.round(
47 categories['best-practices'].score * 100,
48 )
49 const seoScoreMobile = Math.round(categories.seo.score * 100)
50
51 const resDesktop = await fetch(requestUrlForDesktop)
52 const dataDesktop = await resDesktop.json()
53 const { categories: categoriesDesktop } = dataDesktop.lighthouseResult
54 const performanceScoreDesktop = Math.round(
55 categoriesDesktop.performance.score * 100,
56 )
57 const accessibilityScoreDesktop = Math.round(
58 categoriesDesktop.accessibility.score * 100,
59 )
60 const bestPracticesScoreDesktop = Math.round(
61 categoriesDesktop['best-practices'].score * 100,
62 )
63 const seoScoreDesktop = Math.round(categoriesDesktop.seo.score * 100)
64
65 const blocks = [
66 {
67 type: 'section',
68 text: {
69 type: 'mrkdwn',
70 text: `${title}\n${url}`,
71 },
72 },
73 {
74 type: 'section',
75 fields: [
76 {
77 type: 'mrkdwn',
78 text: `*Mobile*\n${scoreWithEmoji(
79 performanceScoreMobile,
80 )}(パフォーマンス)\n${scoreWithEmoji(
81 accessibilityScoreMobile,
82 )}(ユーザー補助)\n${scoreWithEmoji(
83 bestPracticesScoreMobile,
84 )}(おすすめの方法)\n${scoreWithEmoji(seoScoreMobile)}(SEO)`,
85 },
86 {
87 type: 'mrkdwn',
88 text: `*Desktop*\n${scoreWithEmoji(
89 performanceScoreDesktop,
90 )}(パフォーマンス)\n${scoreWithEmoji(
91 accessibilityScoreDesktop,
92 )}(ユーザー補助)\n${scoreWithEmoji(
93 bestPracticesScoreDesktop,
94 )}(おすすめの方法)\n${scoreWithEmoji(seoScoreDesktop)}(SEO)`,
95 },
96 ],
97 },
98 {
99 type: 'divider',
100 },
101 ]
102
103 return blocks
104}
105
106;(async () => {
107 try {
108 const oneWeekBefore = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
109 const updatedArticles = await getArticles({
110 '_sys.updatedAt': { gt: oneWeekBefore.toISOString() },
111 select: ['title', 'slug'],
112 })
113 const updatedArticles = await getArticles()
114
115 const blocks = []
116 if (updatedArticles.length === 0) {
117 blocks.push({
118 type: 'section',
119 text: {
120 type: 'mrkdwn',
121 text: `更新されたページはありませんでした`,
122 },
123 })
124 } else {
125 await Promise.all(
126 updatedArticles.map(async (article) => {
127 const _blocks = await getScores(
128 article.title,
129 `https://hoge.vercel.app/articles/${article.slug}`,
130 )
131 blocks.push(..._blocks)
132 }),
133 )
134 }
135
136 const token = process.env.SLACK_APP_TOKEN
137 const client = new WebClient(token)
138 const channel = process.env.SLACK_CHANNEL_ID + ''
139 const header = [
140 {
141 type: 'section',
142 text: {
143 type: 'mrkdwn',
144 text: '今週のPSIレポートです:pencil:\n:scream:0 - 49\n:rotating_light:50 - 69\n:warning:70 - 89\n:white_check_mark:90 - 100',
145 },
146 },
147 {
148 type: 'divider',
149 },
150 {
151 type: 'section',
152 text: {
153 type: 'mrkdwn',
154 text: `*今週更新された記事*`,
155 },
156 },
157 ]
158
159 await client.chat.postMessage({
160 channel,
161 blocks: [...header, ...blocks],
162 unfurl_links: false,
163 })
164
165 return
166 } catch (err) {
167 console.error(err)
168 }
169})()
これで、以下のような形式でSlackに通知を送れるようになりました。
5. GitHub Actionsを設定する
最後にGitHub Actionsを設定し、定期実行が行われるようにします。
5-1. シークレットの定義
まず、ワークフローで利用するシークレットを定義します。
「Settings」>「Secrets and variables」から「Actions」を選択して、「New repository secret」をクリックします。
NEWT_SPACE_UID
・NEWT_CDN_API_TOKEN
・PSI_API_KEY
・SLACK_APP_TOKEN
・SLACK_CHANNEL_ID
を追加しましょう。
値を入力して「Add secret」をクリックします。
これでワークフローから上記のシークレットを呼び出せるようになりました。
詳細はGitHubの GitHub Actions でのシークレットの使用 のドキュメントをご確認ください。
5-2. ワークフローの定義
.github/workflows/psi.yml
を作成し、ワークフローを定義します。以下のことを行っています。
- schedule を利用して、定期実行のスケジュールを定義する。日本時間ではなく、UTCなので注意する。ここでは毎週月曜日の9時(日本時間)に実行するよう
0 0 * * 1
で定義している - あわせて手動実行も行えるように workflow_dispatch も指定する
- 登録したシークレットを環境変数として設定する
1name: psi
2on:
3 schedule:
4 - cron: '0 0 * * 1'
5 workflow_dispatch:
6jobs:
7 check_psi:
8 runs-on: ubuntu-latest
9 timeout-minutes: 10
10 steps:
11 - uses: actions/checkout@v4
12 - uses: actions/setup-node@v3
13 with:
14 node-version: '18'
15 cache: 'yarn'
16 - run: yarn
17 - name: Check PSI
18 env:
19 NEWT_SPACE_UID: ${{secrets.NEWT_SPACE_UID}}
20 NEWT_CDN_API_TOKEN: ${{secrets.NEWT_CDN_API_TOKEN}}
21 PSI_API_KEY: ${{secrets.PSI_API_KEY}}
22 SLACK_APP_TOKEN: ${{secrets.SLACK_APP_TOKEN}}
23 SLACK_CHANNEL_ID: ${{secrets.SLACK_CHANNEL_ID}}
24 run: node .github/actions/psi
以上で、1週間以内に更新されたURLを対象に、PSIスコアを通知できるようになりました。
以下のようなアレンジも可能ですので、ぜひお試しください。
- ページの更新時に Webhook を利用してPSIスコアを通知する
- 月に1回、主要なページのPSIスコアを通知する