Netlify Formsを使ってブログサイトにコメント機能を追加する

Published: 2020年11月24日 by tomsato

今回の記事について

このブログでは以前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無料枠内での実装を考えているので、無料枠でどの程度まで使えるか

Netlify Pricing

  • フォーム投稿:100回/月まで
  • Serverless Functions:125000リクエスト/月

今回のやり方でコメント機能を作成した際に、月に100コメント以下で収まりそうなら無料枠内で済みそう

今回作成するコメントフォーム&コメント投稿一覧画面について

完成画面

以下のようなコメントフォームと、承認済みコメント投稿一覧を作成する予定

netlify form comment コメント 2

投稿したらすぐに一覧に反映させるのではなく、管理者がその投稿コメントを承認した場合のみ一覧に反映させることとする

内容を入力して「Send」ボタンを押した時はAlertダイアログに「Thank you!」を出して再読み込みを行う

netlify form comment コメント 3

投稿が行われた場合はSlackに通知させ、Slackメッセージ内に承認ボタンを用意して、それが押された場合に承認済みコメント一覧に反映させることとする

netlify form comment コメント 4

「Approve comment」ボタンを押して承認した場合はメッセージが「Comment registered. Site deploying to include it.」というメッセージ内容に書き換わり反映処理が行われるようになる

「Delete comment」ボタンにて削除した場合は「Comment deleted」という内容に書き換わりコメントは削除される

サンプルのソースコード

今回のサンプルは以下のGitHubに乗せているため同じものを作りたいor参考にしたい場合はご覧いただけたらと

https://github.com/tomsato/sample-netlify-comment

構成図

今回Netlify Formsを使ってコメント機能を実装するが構成を知っておかないとわかりづらいので簡単に図にまとめてみた

netlify form comment コメント 1

ポイントとしては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」で作成

netlify form comment コメント 5

Slack通知されたメッセージのボタンを押した時にFunctionsを動作させたいので

「Features」 > 「Interactivity & Shortcuts」にあるInteractivityをOnにして

「https://{サイト名}/.netlify/functions/comment-approve」

とcomment-approveのFunctionsを指定する

netlify form comment コメント 6

また「Features」>「Incoming Webhook」にてWebhookを作成して置く

作成したWebcookは環境変数で設定する

Netlify管理画面での設定

Forms間でデータを移動するためのAPIトークンの作成

「User settings」>「Applications」>「Personal access tokens」で作成できるtoken

netlify form comment コメント 7

作成したAPIトークンは環境変数で設定する

環境変数の設定

「Site settings」>「Build & deploy」>「Environment」より環境変数を設定する

netlify form comment コメント 8

  • 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を作成する、これを叩くことでビルドが自動で走る

netlify form comment コメント 9

Formに投稿があった時にWebhookを叩く

※ これを設定するには一度作成したFormをプッシュ(デプロイ)している必要がありそう

「Site settings」>「Forms」>「Form notifications」よりFormsへ投稿があった時に処理をWebhookを指定する

netlify form comment コメント 10

ここでは2つ追加する

  • sampleCommentForm(コメント投稿があった時):https://{サイト名}/.netlify/functions/comment-recieve
  • sampleApprovedComments(コメントが承認された時):https://api.netlify.com/build_hooks/XXXX (「Build Web hooksの設定」で作成したWebhook)

完成

以上ここまでやってデプロイもすればサイトにコメント機能が追加できるはずです

ですが如何せんやることが多いので抜けがあるかもなので何かあればコメントいただけたらと思いますmm

このサイトにコメントフォームを設置した話

完成画面

こんな感じで置いてみました

netlify form comment コメント 11

サンプルからの差分がいくつかあるのでまとめておきます

フォームやリストに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にしたかったので使ってみました

netlify form comment コメント 12

できなかったこと

投稿完了後にPOSTで受け取るページを静的サイトでどう用意する方法がわからず…(多分できない気が)

formのmethodをGETにした場合にNetlifyがうまく拾ってくれないのでGETにすることもできず、なので現状sweetalertにて確認ダイアログは出しつつ、投稿後にはNetlifyのデフォルトのコメント完了画面が表示されてしまっています

netlify form comment コメント 13

もしNetlify+Jekyllで良い感じにPOSTを受け取るページを用意できるやり方がありそうでしたら教えて頂けたらとmm

コメントを書く

※ 個別に返信が必要な時のみご記入ください

※ Emailは公開されません

※ 承認されると名前・コメントが下記に表示されます

コメント一覧

最近の投稿

ビジュアルリグレッションテストについてまとめ、ネットで調べると数多くのライブラリがありどれがどんな立ち位置なのか全体像がわかりずらかったのでどんな種類があるのか入門の入門としてまとめます、またPlaywrightを使って実際に触ってみました

社内ツールなどの超小規模なAPIをGolangで実装する際にフレームワークを使うべきかを、実際にnet/httpを使った実装とフレームワークを使った実装を比較することでどれだけ優位性があるかを見ていきたいと思います。今回はフレームワークにはシンプルで使いやすそうなEchoを使うことにします。

vue-pdfを使ってNuxt.jsで作成しているアプリケーションに pdfスライドを表示させるサンプルを作成しました README.md通りに実装してもうまくいかないところがあったのでそのあたり含めてまとめます

Vue.js / Nuxt.jsにおけるログインの実装方法をまとめる Auth0やNuxt.jsのAuth Moduleとmiddlewareについて調べつつサンプルを作成することで理解を深める

コンポーネント設計について考える Atomic DesignやPresentational Component, Container Componentについてまとめつつ 自分だったらVue.js / Nuxt.jsでどういうコンポーネント設計にするかについてまとめます

カテゴリ一覧

タグ一覧