この組み合わせはここ数年のトレンドですかね。直近で言うと、東京都が作った新型コロナウィルス対策サイトがこの構成で作成され公開されたと話題になりました。
東京都 新型コロナウイルス感染症対策サイト
https://stopcovid19.metro.tokyo.lg.jp/
このサイトはソースコードもGitHubで公開されており、大変参考になります。
tokyo-metropolitan-gov / covid19
https://github.com/tokyo-metropolitan-gov/covid19
今回はそんなNuxt.js + Netlifyでお問い合わせフォームを作ってみます。
Nuxt(ナクスト)はVue.jsをベースにしたJavaScriptフレームワークです。
そもそもVue.jsもフレームワークなのですが、そのまま扱うには自分でWebpackを設定したりルーターを設定したりけっこう手間がかかります。それに比べてNuxtはWebpackの設定もルーターの設定も含め、Webアプリケーションを作るために必要なVue.jsの設定が最初からほとんど組み込まれています。そして1番の違いは、SSR(サーバーサイドレンダリング)がごく簡単に使えることでしょうか。SSRは自前で実装するにはかなりハードルが高い印象ですが、Nuxtであればnodeが動く環境であればすぐ使えます。また、それでもSSRはハードルが高いなっていう場合でも、静的サイトをコマンド一つで簡単に生成できる機能も備わっています。
扱いやすくてすぐに使えるので、ここ数年フロントエンド界隈では人気の高いフレームワークとなっているようです。
静的サイト向けの高機能ホスティングサービスです。
これによりいわゆる「サーバーレス開発」が可能になります。
Gitと連携して自動デプロイ、無料SSLを利用可、独自ドメインを当てることも可能です。なんと言っても1番良いところは、Freeプランがあるのでほぼ無料。
無料枠が大きいため、かなり大規模なサイトなど特別な事情がない限りはほとんどのサイトが無料で使えると思います。
サーバーがないとメールフォームとかどうすんの?とか思いますが、今回実装するNetlify Formsという機能でメールフォームを機能させることができますのでご安心を。ただしFreeプランだと上限があり100通/月までなので頻繁にお問い合わせがあるサイトは有料に切り替えるか別の方法をご検討ください。
またNetlify Functionsという機能でAWSアカウントなしでLambdaを利用できます。どうしてもサーバー側に機能を追加したい場合はこちらを使うと良いかと思います。(125,000リクエスト/月、実行時間100時間/月)
今回はこちらを使ってNetlify Formsに付いていないお問い合わせフォームの自動返信機能を作ってみたいと思います。
今回はNuxtで作ったサイトをNetlifyで静的にビルドして配信します。その中でNetlify Formsでフォームを、Netlify Functionsでフォームの自動返信メールを作る方法をメモ。
毎回デモサイトを作っているのですが、今回はお問い合わせフォームという特性上デモの公開は控えさせていただきます。閲覧数の少ないブログではありますが、自動返信のテストを頻繁にされてもちょっと困るので・・・笑
デモを公開しないため、CSS・デザイン面についてもここで書きません。好みのフレームワークを使ったりして自身で調整してください。
また、長くなるのでNetlifyの登録やGitHubとの連携などについても省略させていただきますのでご了承ください。Nuxt + Netlifyでサイトを公開するまでの手順については下記参考サイトを貼っておきます。
Nuxt.js + Netlifyで爆速構築するサーバーレス開発入門
https://qiita.com/isihigameKoudai/items/e3b136e9964f1d30d73d
Nuxt + Contentful + Netlifyでブログ作成全手順
https://izm51.com/posts/nuxt-contentful-netlify-blog-making-1/
Netlify Formsを使ってフォームを書くのは非常に簡単。form
要素にnetlify
、もしくはdata-netlify="true"
という属性と追加するだけです。
<form name="contact" method="POST" netlify>
<!-- フォームの内容 -->
</form>
または
<form name="contact" method="POST" data-netlify="true">
<!-- フォームの内容 -->
</form>
どちらでも好きな方でOKです。
またNuxtでNetlify Forms機能を使う場合はhidden
フィールドを埋め込む必要があります。
<form name="contact" method="POST" data-netlify="true">
<!-- ↓これを追加 -->
<input type="hidden" name="form-name" value="contact" />
<!-- フォームの内容 -->
</form>
普通のhtmlだったらNetlify側が勝手にこのフィールドを埋め込んでくれるみたいなのですが、Nuxtの場合は外からDOM追加できないのでエラーになります。そのため手動で先に埋め込んでおきます。参考は下記です。
楽勝すぎるはずの netlify form を nuxt generate で頑張って使う
https://qiita.com/yahsan2/items/a70c4c8f617ee9b1f9ff
これを踏まえてpages/contact/index.vue
にフォームを作ることにします。
まずはテンプレート部分を書いていきます。
<template>
<div class="p-contact">
<form class="p-contact__form" name="contact" method="POST" data-netlify="true" data-netlify-honeypot="bot-field" @submit.prevent="onSubmit" :class="sendingClass">
<input type="hidden" name="form-name" value="contact">
<div class="p-contact__item">
<label for="username">お名前</label>
<input type="text" id="username" name="username" v-model="username" autocomplete="name">
</div>
<!-- /.p-contact__item -->
<div class="p-contact__item">
<label for="katakana">フリガナ</label>
<input type="text" id="katakana" name="katakana" v-model="katakana">
</div>
<!-- /.p-contact__item -->
<div class="p-contact__item">
<label for="useremail">メールアドレス</label>
<input type="text" id="useremail" name="useremail" v-model="useremail" autocomplete="email">
</div>
<!-- /.p-contact__item -->
<div class="p-contact__item">
<label for="message">お問い合わせ内容</label>
<textarea id="message" name="message" v-model="message"></textarea>
</div>
<!-- /.p-contact__item -->
<div class="p-contact__item" v-show="false">
<label for="message">スパムでない場合は空欄</label>
<input type="text" name="bot-field" v-model="botField"/>
</div>
<!-- /.p-contact__item -->
<div class="p-contact__submit">
<button type="submit">送信</button>
</div>
<!-- /.p-contact__submit -->
</form>
<!-- /.p-contact__form -->
</div>
</template>
内容は「名前」「フリガナ」「メールアドレス」「お問い合わせ内容」の4項目。フォームはAjaxで送信したいのでform
のsubmitイベントを@submit.prevent
で無効化してonSubmit
関数を指定しています。Ajax処理については後ほど。
また、スパム対策にNetlify Formsの機能でハニーポットを設定してみました。
form
要素にdata-netlify-honeypot="bot-field"
をつけてハニーポット用のフィールドを作ります。ハニーポット用のフィールドに入力があった場合にスパムと判定されフォームが送信されません。このフィールドは人間の場合は見えなくでもいいのでv-show=false
で非表示にしておきます。
<div class="p-contact__item" v-show="false">
<label for="message">スパムでない場合は空欄</label>
<input type="text" name="bot-field" v-model="botField"/>
</div>
フォームなのでバリデーションをつけたいですよね。特にNetlifyの場合はサーバーレスでバリデートを裏側で処理できないためほぼ必須です。
いろいろと調べると僕はVeeValidateが使いやすそうで気に入ったので使ってみることにしました。
まずYarnでインストール
yarn add -D vee-validate
プラグインとしてVeeValidateを読み込みます。plugins/vee-validate.js
を作成して以下のように書きます。
import Vue from 'vue'
import {
extend,
ValidationProvider,
ValidationObserver,
Validator,
localize
} from 'vee-validate'
import ja from 'vee-validate/dist/locale/ja.json'
import { required, email, max } from 'vee-validate/dist/rules'
//日本語
localize('ja', ja)
//必須
extend('required', required)
//メールアドレス
extend('email', email)
//文字数上限
extend('max', max)
//カタカナのみ
extend('katakana', {
message: (field) => { return field + "は全角カタカナのみ使用できます" },
validate: (value) => { return /^[ァ-ン]+$/.test(value) }
})
Vue.component('validation-provider', ValidationProvider)
Vue.component('validation-observer', ValidationObserver)
こんな感じでextend
でバリデーションの条件を追加していきます。
カタカナの判定はデフォルトになかったのでメッセージと判定条件を追加しました。
このプラグインをnuxt.config.js
で読み込みます。
plugins: [
'~/plugins/vee-validate' //これを追記
],
あとはソースコードをVeeValidateの書き方に変更。
<template>
<div class="p-contact">
<validation-observer ref="observer" v-slot="{ invalid, validated }" tag="form" class="p-contact__form" name="contact" method="POST" data-netlify="true" data-netlify-honeypot="bot-field" @submit.prevent="onSubmit" :class="sendingClass">
<input type="hidden" name="form-name" value="contact">
<div class="p-contact__item">
<label for="username">お名前</label>
<validation-provider v-slot="{ errors }" rules="required|max:100" name="お名前">
<input type="text" id="username" name="username" v-model="username" autocomplete="name">
<p v-show="errors.length" class="p-contact__error">{{ errors[0] }}</p>
</validation-provider>
</div>
<!-- /.p-contact__item -->
<div class="p-contact__item">
<label for="katakana">フリガナ</label>
<validation-provider v-slot="{ errors }" rules="required|katakana" name="フリガナ">
<input type="text" id="katakana" name="katakana" v-model="katakana">
<p v-show="errors.length" class="p-contact__error">{{ errors[0] }}</p>
</validation-provider>
</div>
<!-- /.p-contact__item -->
<div class="p-contact__item">
<label for="useremail">メールアドレス</label>
<validation-provider v-slot="{ errors }" rules="required|email|max:256" name="メールアドレス">
<input type="text" id="useremail" name="useremail" v-model="useremail" autocomplete="email">
<p v-show="errors.length" class="p-contact__error">{{ errors[0] }}</p>
</validation-provider>
</div>
<!-- /.p-contact__item -->
<div class="p-contact__item">
<label for="message">お問い合わせ内容</label>
<validation-provider v-slot="{ errors }" rules="required|max:1000" name="お問い合わせ内容">
<textarea id="message" name="message" v-model="message"></textarea>
<p v-show="errors.length" class="p-contact__error">{{ errors[0] }}</p>
</validation-provider>
</div>
<!-- /.p-contact__item -->
<div class="p-contact__item" v-show="false">
<label for="message">スパムでない場合は空欄</label>
<input type="text" name="bot-field" v-model="botField"/>
</div>
<!-- /.p-contact__item -->
<div class="p-contact__submit">
<button type="submit" :disabled="invalid || !validated">送信</button>
</div>
<!-- /.p-contact__submit -->
</form>
<!-- /.p-contact__form -->
</div>
</template>
エラーメッセージの出し方は調べるといろんな書き方が出てくるのでこの辺りは調べて自分に合ったものを設定してもらえれば良いかと思います。
これでバリデーション機能のついたフォームになりました!
Netlify側で初期設定みたいなものは、なんと「なし」です。
先ほどform
要素にnetlify
、もしくはdata-netlify="true"
という属性と追加しましたが、それをそのままデプロイするだけで利用できます。
デプロイしてNetlify がformを認識すると、Welcomeメールが届いてFormsを利用できるようになったと通知されます。
フォーム送信された時の通知設定はNetlifyの画面から設定できます。
Setting -> Forms -> Form notificationsから右上の「Add noticication」ボタンをクリックすると「Slack」「Webhook」「Eメール」の3つの通知方法を選択できます。
僕はEメールにしましたが、他も簡単そうだし設定が楽でいいなと思います。
IntegromatでWebhook使えばメールもSlackもその他のツールにもまとめて通知できるかも。今度やってみようかな。
ここからJavaScriptを書きます。
テンプレートと分けて書いていますが、同じpages/contact/index.vue
の中身です。
<script>
export default {
data() {
return {
username : '',
katakana : '',
useremail : '',
message : '',
botField : '',
isSubmit : false,
isSending : false,
isError : false,
completeMessage : '',
}
},
computed: {
sendingClass(){
return {
'is-sending' : this.isSending,
'is-error' : this.isError,
'is-complete' : this.isSubmit
};
}
},
methods: {
onSubmit() {
if(this.isSending){
return;
}
this.isSending = true;
this.completeMessage = '送信処理中…';
const params = new URLSearchParams();
params.append('form-name', 'contact');
params.append('username', this.username);
params.append('katakana', this.katakana);
params.append('useremail', this.useremail);
params.append('message', this.message);
if(this.botField){
params.append('bot-field', this.botField);
}
this.$axios
.$post('/', params)
.then(() => {
this.completeMessage = 'お問い合わせを送信しました!';
this.resetForm();
this.isSubmit = true;
})
.catch(err => {
this.completeMessage = 'お問い合わせの送信が失敗しました';
this.isError = true;
})
.finally(() => {
this.isSending = false;
});
},
resetForm() {
this.username = '';
this.katakana = '';
this.useremail = '';
this.message = '';
this.isError = false;
this.$refs.observer.reset();
}
}
}
</script>
axiosを使ってAjaxでフォーム内容を送信します。form-name
のhiddenフィールドとハニーポッド用のbotField
を含めるのを忘れずに。
onSubmit() {
if(this.isSending){
return;
}
this.isSending = true;
this.completeMessage = '送信処理中…';
const params = new URLSearchParams();
params.append('form-name', 'contact');
params.append('username', this.username);
params.append('katakana', this.katakana);
params.append('useremail', this.useremail);
params.append('message', this.message);
if(this.botField){
params.append('bot-field', this.botField);
}
this.$axios
.$post('/', params)//POST先はこのページでOK
.then(() => {
this.completeMessage = 'お問い合わせを送信しました!';
this.resetForm();
this.isSubmit = true;
})
.catch(err => {
this.completeMessage = 'お問い合わせの送信が失敗しました';
this.isError = true;
})
.finally(() => {
this.isSending = false;
});
},
また、「送信中」「送信エラー」「送信完了」のフラグを用意しておくことで、二重送信の防止や、それぞれのフラグに合わせた色の変化やメッセージの表示などに使えるのでフラグを作っています。
computed: {
//フラグによってClassの付け外しをすることで簡単に色などのデザイン変更が可能
sendingClass(){
return {
'is-sending' : this.isSending,
'is-error' : this.isError,
'is-complete' : this.isSubmit
};
}
},
唐突にでてきたaxiosについてはVueではおなじみのAjax用のライブラリですね。使い方の説明は省略しますが、今回はNuxt用に@nuxtjs/axios
を使いました。
このソースコードでは使っていませんが、completeMessage
に送信完了メッセージを表示させられるようにしているので、トーストポップアップみたいなUIとうまく組み合わせるなどして送信完了のUIも作れます。
送信完了のために別ページに遷移させる場合はAjaxを使う必要がないので、form
要素を下記のように変更すれば良いかと思います。
<validation-observer ref="observer" v-slot="{ invalid, validated }" tag="form" class="p-contact__form" name="contact" method="POST" action="mail-sent" data-netlify="true" data-netlify-honeypot="bot-field" :class="sendingClass">
<!-- フォームの内容 -->
</form>
@submit.prevent="onSubmit"
を削除し通常のForm送信にするaction="mail-sent"
を追加し、/mail-sentという送信完了ページに遷移ここまででフォームは完成!
送信してみるとメール通知もバッチリです。
通知の文面については本当に必要最低限のもので、タイトル含め変更はできませんが、内容わかるし全然問題ないと思います。許せない方は別の方法を検討しましょう。
ここまででフォームとしては完成なのですが、1つ足りない点が。
管理者宛てに通知は来ますが、ユーザーには自動返信機能がないためお問い合わせ完了の通知が送れません。自動返信はなくてもいいっちゃいいけど、個人的には確認のためにあった方が親切かなと思います。
付けましょう。
Formsと同じようにデプロイした時にFunctionsのファイルがあるかどうかを認識するとWelcomeメールが来て使えるようになる仕組みですが、 Functionsの場合は設定ファイルが必要になります。
netlify.toml
というファイルをルートディレクトリ(nuxt.config.js
と同階層)に作成し、以下のように書きます。
[build]
command = "yarn build"
functions = "functions"
publish = "dist"
command
= デプロイ時に実行するコマンドfunctions
= Netlify Functionsのファイルを入れるディレクトリpublish
= 公開ディレクトリ
これでルートディレクトリのfunctions
内にファイルを置くことでNetlify Functionsを実行できるようになります。
Netlify Functionsは自由に簡易なAPIを作ることもできるのですが、いくつかトリガーも用意されています。今回はsubmission-created
というFormsの送信に合わせて実行できるトリガーを使って自動返信メールを送信する機能を作ってみます。
使い方は簡単。先ほどnetlify.toml
で設定したfunctions
ディレクトリを作成し、その中にsubmission-created.js
という名前のファイルを作ります。これでこのファイルの中に書いた処理がFormsの送信時に実行されます。
あとはnodemailerというnodeでメール送信できるモジュールを使って自動返信処理を書いていきます。
まずYarnで必要なモジュールをインストール
yarn add -D nodemailer dotenv
そして処理内容を書きます。
functions/submission-created.js
require('dotenv').config();
const nodemailer = require('nodemailer');
exports.handler = function(event, context, callback) {
const { username, katakana, useremail, message } = JSON.parse(event.body).payload.data;
// OAuth認証情報
const auth = {
type : 'OAuth2',
user : process.env.OAUTH_USER,
clientId : process.env.OAUTH_CLIENT_ID,
clientSecret : process.env.OAUTH_CLIENT_SECRET,
refreshToken : process.env.OAUTH_REFRESH_TOKEN
};
// トランスポート
const transport = {
service : 'gmail',
auth : auth
};
let transporter = nodemailer.createTransport(transport);
const url = 'https://notes-sharesl.netlify.app/';
let mailOptions = {
from : `notes by SHARESL <info@sharesl.net>`,
to : `${useremail}`,
subject : '【notes by SHARESL】お問い合わせありがとうございます',
text : `${username} 様\n\nお問い合わせありがとうございます。\n以下の内容でフォームを送信いたしました。\n数日中に追って担当者よりメールにて回答をお送りいたします。\n今しばらくお待ちください。\n\n------ 送信内容 ------\n【お名前】\n${username}\n\n【フリガナ】\n${katakana}\n\n【メールアドレス】\n${useremail}\n\n【お問い合わせ内容】\n${message}\n\n--------------------\nnotes by SHARESL\n${url}\n`,
};
transporter.sendMail(mailOptions, function(error, info) {
if (error) {
callback(error);
} else {
callback(null, {
statusCode: 200,
body: 'ok',
});
}
});
};
送信にはメールサーバーが必要なのでG SuiteアカウントのGmailを使いました。SMTP接続はGoogleのセキュリティ上では良くないとのことでOAuth認証を使うことにしました。その内容については下記を参考にしました。
Node.jsでGmailからメールをOAuth認証で送信する方法
https://qiita.com/markey/items/f0a2d5215ec428442673
nodemailerは初めて使ったのですが、Netlify上での使い方を探すとちょうど同じことをやっている方の記事があったのでこちらを参考にしました。
Netlify functionを使ってフォームが送信されたときに送信したユーザーのメールアドレスに自動返信する。
https://qiita.com/snemesk/items/355951ee36b5e5cdf0c7
process.env.OAUTH_USER
などの環境変数を使っていますが、こちらはNetlify上で設定することができます。clientId、clientSecret、refreshTokenは流出すると危険なため直書きせずに環境変数に入れてしまいます。
Netlify上の環境変数の設定は、
Setting -> Build & deploy -> Environment -> Environment variables から追加できます。
netlify.toml
に書く方法でも設定できるみたいですが、Gitに上がってしまうのでこの方法を使うことはないと思います。
うまく自動返信できるようになりました。
今回はNetlifyでお問い合わせフォームを作る方法についてメモしました。
ポートフォリオとかこのブログぐらいの小規模サイトであれば全然問題なく使えそうな感じ。
なんといってもNetlifyもNuxtも使いやすいのがいいですね。
ビルド環境を新しくしたり今まで使ってたものを更新したりとかって、自分で環境作ってたら必要だけど、Nuxtだとそんなの必要ないですし。Netlifyだとサーバーのメンテも必要ないですし、静的だからセキュリティ面も安心。おまけに表示速度も高速。全部揃ってるこの構成なら無駄な時間をほとんどかけずに制作に集中できますよね。どんどん使っていきたいと思います。
CMSとの連携はContentfulが良く話題になってて使い勝手は悪くなさそうだけど、どこまで自由にカスタマイズできるんだろうか?
このブログはWordPressで今からContentfulに移行っていうのはちょっと難しそう。時間がある時にREST APIと連携して試してみようかなーと思います。