今回の記事について
このブログでは以前WordPressを使っていましたがAWSなどの料金がかかっていたため無料で使えるGitHub Pages+Jekyllに移行をしました
WordPressから静的ホスティングサイトに移行したことによる個人的な一番のデメリットとしては「コメント機能が無い」ことだと思っていたのですが追加するためには
- サーバやDBなどは自分で用意して自分で実装する
- 商業のものを利用する
のどれかで頑張る必要がありました
商業のものを無料枠内で使うことを前提にした場合
- Disqus(1番メジャーっぽい):アクセスが多いサイトには広告が表示されるようになり、表示させたく無い場合は有料プランに切り替えなければならない
- JustComments:無料枠が決められておりクレジットを使い切るとそれ以上コメントの取得や投稿などができなくなる
など中々条件が厳しいものとなっており、自分で実装するのも面倒だったためコメント機能については一旦諦めてブログサイトをGitHub Pages+Jekyllに移行を行っていました
その後Netlifyの存在を知って、プライベートリポジトリのものをホスティングできたりビルドコマンドが定義できたり、小規模サイトなら無料枠の中でFunctions, Split Testing, Netlify Formsなど便利な機能を含めて使えるということでNetlify+Jekyllに移行しました
その中でふと「Netlify Formsでコメント機能を追加できるのでは?」と思い今回調べてみることにしました
通常Netlify Formsは問い合わせフォームでの利用が想定されていますが、Netlify Formsを使うことで一から実装するよりは楽に用意できるので興味がある人は試してもらえればと思います
この記事の対象読者
- Netlifyで立ち上げているブログサイトにコメント機能を追加したい人
- Netlify FormsとFunctionsについて基本的なことを知っている人(知らない人は事前に下記参考リンクを見ていただけたらと)
参考リンク
Netlifyについて基本的な使い方については以下の記事を参考に
Netlifyの使い方入門 無料料金枠や独自ドメイン設定方法について
Netlify FormsやFunctionsのそもそもの話は以下のページを参考に(別機能であるSplit TestingやFunctionsについても記載している)
Netlifyの標準機能であるFormsやSplit Testing、Functionsの使い方まとめ
Netlify無料枠について
今回はNetlify無料枠内での実装を考えているので、無料枠でどの程度まで使えるか
- フォーム投稿:100回/月まで
- Serverless Functions:125000リクエスト/月
今回のやり方でコメント機能を作成した際に、月に100コメント以下で収まりそうなら無料枠内で済みそう
今回作成するコメントフォーム&コメント投稿一覧画面について
完成画面
以下のようなコメントフォームと、承認済みコメント投稿一覧を作成する予定
投稿したらすぐに一覧に反映させるのではなく、管理者がその投稿コメントを承認した場合のみ一覧に反映させることとする
内容を入力して「Send」ボタンを押した時はAlertダイアログに「Thank you!」を出して再読み込みを行う
投稿が行われた場合はSlackに通知させ、Slackメッセージ内に承認ボタンを用意して、それが押された場合に承認済みコメント一覧に反映させることとする
「Approve comment」ボタンを押して承認した場合はメッセージが「Comment registered. Site deploying to include it.」というメッセージ内容に書き換わり反映処理が行われるようになる
「Delete comment」ボタンにて削除した場合は「Comment deleted」という内容に書き換わりコメントは削除される
サンプルのソースコード
今回のサンプルは以下のGitHubに乗せているため同じものを作りたいor参考にしたい場合はご覧いただけたらと
https://github.com/tomsato/sample-netlify-comment
構成図
今回Netlify Formsを使ってコメント機能を実装するが構成を知っておかないとわかりづらいので簡単に図にまとめてみた
ポイントとしてはFormsとFunctionsをそれぞれ2つづつ用意している
処理の流れ
- コメントが投稿される
- Netlify Forms(sampleCommentForm)に承認待ちコメントとして投稿データが保存される
- Netlify Forms(sampleCommentForm)にデータが入ったことでWebHookでNetlify Functions(comment-recieve)を叩く
- Netlify Functions(comment-recieve)では管理者にSlack通知を行う
- 管理者がSlack通知を受け、メッセージ内にある「Approve comment」ボタンを押してコメントを承認する
- 承認するとNetlify Functions(comment-approve)が動いてsampleCommentFormにある承認待ちコメントリストの投稿データをsampleApprovedCommentsに移動する
- Netlify Functions(comment-approve)完了後にWebHookでNetlifyが再デプロイされる
- デプロイ時にnetlify-plugin-form-submissionsプラグインによってsampleApprovedCommentsにある承認済み投稿データを取得して所定ディレクトリにJson形式で配置した状態でデプロイが行われる
- 承認済みコメントリストは所定ディレクトリに配置されたファイルを読み込んで表示する
- 承認後にユーザーが承認済みコメントリストを見ると、コメントが追加されていることを確認できる
Netlify Formsに格納されているデータを取得するAPIがあるのでそれを使って取得でも良いが、ここではnetlify-plugin-form-submissionsプラグインを使って、デプロイビルド時に取得してファイルに配置する方法を採用する
作成手順
Netlify Formsの作成(フォーム画面の作成)
コメントフォーム
public/index.html
内に記述する(違うパスに置く場合はhidden pathに記載するパスも変更するのを忘れずに)
Netlifyの設定でpublicディレクトリ以下のソースコードを公開する予定
<form id="my-form" action="/" name="sampleCommentForm" method="post" netlify-honeypot="bot-field" netlify>
<p>
<label>Name: <input type="text" name="name" /></label>
</p>
<p>
<label>Email(任意): <input type="email" name="email" /></label>
</p>
<p>
<label>Message: <textarea name="content"></textarea></label>
</p>
<p style="display:none;">
<label>Bot Field(ここに値が入るとスパム判定する): <input name="bot-field"/></label>
</p>
<div>
<input type="submit" value="Send" />
<p>※ コメントは承認されると表示されます</p>
<input type="hidden" name="path" value="index.html" />
</div>
</form>
以下の情報を送信する
- name:名前
- email:メールアドレス(なくても良いが個別に返信が欲しい人用に念のため)
- content:コメント内容
- bot-field:ここに値が入るとスパム判定になる
- path:URLパス、Slack通知を受け取る際にどこのページにコメントされたかを確認する、またコメントリストを表示する際にそのページでコメントされたもののみ取得するための判定用キー
ダミーコメントフォーム
Netlify Formsは「承認待ちコメント一覧」と「承認済みコメント一覧」のデータを格納する箱として2つを用意する必要があるが
画面として用意するのは「承認待ちコメント一覧」だけでよく、「承認済みコメント一覧」を実際に画面に表示させる必要はない
しかしNetlify Formsは一度でもソースとしてプッシュしないと作成することができないので、「/dummy.html」に配置するなど基本的にユーザーが到達することがありえないページを用意しておくか CSSやJavaScriptで非表示にするなどの工夫が必要
ここでは public/dummy.html
に配置する
<form name="sampleApprovedComments" method="post" netlify netlify-honeypod="bot-field">
<input type="text" name="name">
<input type="email" name="email">
<textarea name="content"></textarea>
<input type="hidden" name="path">
<input name="bot-field" />
</form>
Functionsの作成
コメントが投稿された時に動くFunctions
functions/comment-recieve.js
に配置する
var request = require("request")
exports.handler = function(event, context, callback) {
const body = JSON.parse(event.body)
const slackURL = process.env.SLACK_WEBHOOK_URL
const slackPayload = {
"text": "New comment on " + process.env.URL,
"attachments": [
{
"fallback": "New comment",
"color": "#444",
"author_name": body.data.name + ' ' + body.data.email,
"title": body.data.path,
"title_link": process.env.URL + body.data.path,
"text": body.data.content
},
{
"fallback": "Manage comments on " + process.env.URL,
"callback_id": "comment-action",
"actions": [
{
"type": "button",
"text": "Approve comment",
"name": "approve",
"value": body.id
},
{
"type": "button",
"style": "danger",
"text": "Delete comment",
"name": "delete",
"value": body.id
}
]
}]
}
request.post({ url: slackURL, json: slackPayload }, function (err, httpResponse, body) {
let msg = ''
if (err) {
msg = 'Post to Slack failed:' + err
} else {
msg = 'Post to Slack successful! Server responded with:' + body
}
callback(null, {
statusCode: 200,
body: msg
})
return console.log(msg)
})
}
環境変数として「SLACK_WEBHOOK_URL」「URL」が必要(後ほど設定予定)
コメントが承認された時に動くFunctions
functions/comment-approve.js
に配置する
var request = require("request")
function purgeComment(id) {
var url = `https://api.netlify.com/api/v1/submissions/${id}?access_token=${process.env.NETLIFY_AUTH_TOKEN}`
request.delete(url, function (err, response, body) {
if (err) {
return console.log(err)
} else {
return console.log("Comment deleted from queue.")
}
})
}
exports.handler = function (event, context, callback) {
const body = event.body.split("payload=")[1]
const payload = JSON.parse(unescape(body))
const method = payload.actions[0].name
const id = payload.actions[0].value
if (method == "delete") {
purgeComment(id)
callback(null, {
statusCode: 200,
body: "Comment deleted"
})
} else if (method == "approve") {
const url = `https://api.netlify.com/api/v1/submissions/${id}?access_token=${process.env.NETLIFY_AUTH_TOKEN}`
request(url, function (err, response, body) {
if (!err && response.statusCode === 200) {
const data = JSON.parse(body).data
// now we have the data, let's massage it and post it to the approved form
const payload = {
'form-name': "sampleApprovedComments",
'path': data.path,
'received': new Date(),
'email': data.email,
'name': data.name,
'content': data.content
}
const approvedURL = process.env.URL
console.log("Posting to", approvedURL)
console.log(payload)
request.post({
url: approvedURL,
headers: {
"Content-type": "application/x-www-form-urlencoded",
},
form: payload
}, function (err, httpResponse, body) {
let msg
if (err) {
msg = 'Post to approved comments failed:' + err
console.log(msg)
} else {
msg = 'Post to approved comments list successful.'
console.log(msg)
purgeComment(id)
}
msg = "Comment registered. Site deploying to include it."
callback(null, {
statusCode: 200,
body: msg
})
return console.log(msg)
})
}
})
}
}
承認済みコメント一覧を表示するための記述
Pluginの追加
netlify.toml
に以下を追加する
[build]
functions = "functions/"
publish = "public/"
[[plugins]]
package = "netlify-plugin-form-submissions"
[plugins.inputs]
formNames = "sampleApprovedComments"
dataDirectory = "public/_data"
package.json
にpluginを追加する
{
"dependencies": {
"netlify-plugin-form-submissions": "^0.1.4",
"request": "^2.88.2"
}
}
Functions内で使うrequestモジュールも追加するのを忘れずに
これでsampleApprovedCommentsのデータを読み込んでpublic/_dataに配置されることになる
従ってこのファイルを読み込んで表示する処理が必要
Jsonファイルを読み込んで承認済みコメント一覧を記述する
public/index.html
に追記
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script language="javascript" type="text/javascript">
// 承認されたコメント一覧を表示
const url = '/_data/sampleApprovedComments_submissions.json'
$.getJSON(url, (json) => {
for (let i = 0; i < json.length; i++) {
if (json[i].data.path !== 'index.html') {
continue
}
$('#approvalCommentsList').append(`<li>Name: ${json[i].data.name}, Message: ${json[i].data.content}</li>`)
}
})
// 送信前に一言感謝
$("#my-form").submit(function(e) {
alert("Thank you!");
});
</script>
ブログにコメント機能を追加することを考えると、そのページでコメントされたものだけを表示したい
コメントフォームのhiddenにてpathも追加するようにしたのでここではそのpathを見て表示対象を絞ることでそのページでコメントされたものだけを表示することができる
json[i].data.path !== 'index.html'
Slack Appの準備
Slack Appを作成するページへいき、新しくAppを作成する
App名を「BlogCommentApp」で作成
Slack通知されたメッセージのボタンを押した時にFunctionsを動作させたいので
「Features」 > 「Interactivity & Shortcuts」にあるInteractivityをOnにして
「https://{サイト名}/.netlify/functions/comment-approve」
とcomment-approveのFunctionsを指定する
また「Features」>「Incoming Webhook」にてWebhookを作成して置く
作成したWebcookは環境変数で設定する
Netlify管理画面での設定
Forms間でデータを移動するためのAPIトークンの作成
「User settings」>「Applications」>「Personal access tokens」で作成できるtoken
作成したAPIトークンは環境変数で設定する
環境変数の設定
「Site settings」>「Build & deploy」>「Environment」より環境変数を設定する
- NETLIFY_AUTH_TOKEN:NetlifyのAPIトークン
- URL:サイトのURL
- SLACK_WEBHOOK_URL:Slack Appの「Features」>「Incoming Webhook」にてWebhookを作成したURL
自動ビルドするためにBuild Web hooksの設定
「Site settings」>「Build & deploy」>「Continuous Deployment」>「Build hooks」よりWebhookを作成する、これを叩くことでビルドが自動で走る
Formに投稿があった時にWebhookを叩く
※ これを設定するには一度作成したFormをプッシュ(デプロイ)している必要がありそう
「Site settings」>「Forms」>「Form notifications」よりFormsへ投稿があった時に処理をWebhookを指定する
ここでは2つ追加する
- sampleCommentForm(コメント投稿があった時):https://{サイト名}/.netlify/functions/comment-recieve
- sampleApprovedComments(コメントが承認された時):https://api.netlify.com/build_hooks/XXXX (「Build Web hooksの設定」で作成したWebhook)
完成
以上ここまでやってデプロイもすればサイトにコメント機能が追加できるはずです
ですが如何せんやることが多いので抜けがあるかもなので何かあればコメントいただけたらと思いますmm
このサイトにコメントフォームを設置した話
完成画面
こんな感じで置いてみました
サンプルからの差分がいくつかあるのでまとめておきます
フォームやリストにCSSを当てて見た目をそれっぽくした
CSSフレームワークにはBulmaを使っていたのでその中でそれっぽいものを割り当ててみました
コメント投稿時間を付与
「送信前に一言感謝」のところでalertを出す前にform日付を付与させています
// 日付付与の例
const form = $(".comment-form")
const dt = new Date()
const date = dt.getFullYear()
+ '年' + ('0' + (dt.getMonth() + 1)).slice(-2)
+ '月' + ('0' + dt.getDate()).slice(-2)
+ '日 ' + ('0' + dt.getHours()).slice(-2)
+ '時' + ('0' + dt.getMinutes()).slice(-2)
+ '分'
$('<input>').attr({
'type': 'hidden',
'name': 'date',
'value': date
}).appendTo(form);
input name=dateが増えたのでformなど各種修正が必要です、サンプルに反映できておらずすみませんmm
確認ダイアログはsweetalertを使う
いい感じのUIにしたかったので使ってみました
できなかったこと
投稿完了後にPOSTで受け取るページを静的サイトでどう用意する方法がわからず…(多分できない気が)
formのmethodをGETにした場合にNetlifyがうまく拾ってくれないのでGETにすることもできず、なので現状sweetalertにて確認ダイアログは出しつつ、投稿後にはNetlifyのデフォルトのコメント完了画面が表示されてしまっています
もしNetlify+Jekyllで良い感じにPOSTを受け取るページを用意できるやり方がありそうでしたら教えて頂けたらとmm
コメントを書く
コメント一覧