「Gatsby x TypeScript」でブログを作りました (3. サードパーティーの使う上での豆知識)
Gatsbyでブログを作った人の最初の記事と言えば、だいたい「Gatsbyでブログ作りましたー」でしょう。なので許してください。
「Gatsby x TypeScript」でブログを作る時に役に立った情報などを自分用のメモというか棚卸しというかまとめていきたいと思います。
このブログ一見シンプルに見えて、ワードプレスで作られたブログと引けを取らないくらい実はいろんな機能が盛り込まれてます。
- Disqusによるブログコメント投稿機能
- Algoliaによる全文検索機能
- Mailgunによる自動返信メール(お問い合わせ時)
- Google reCAPTCHA v3 (サーバーサイドバリデーションもあり)
- Google Analytics
- Iframelyによる記事へのiframeの埋め込み機能
- JSONLDを使った構造化マークアップ(SEO対策)
全4回に分けて、gatsbyのプラグインで一瞬で実装が終わったものもあればそうでないものもあるので実装する上で参考になったサイトと要点を軽くまとめなながら紹介できたらいいかと思います。
「Gatsbyでのブログ作成手順」は他のブログにあるので手順に関しては紹介しません。
この投稿では 3.サードパーティーの使う上での豆知識
について書こうと思います。
使い方は他のブログ記事で紹介されてますからね。
Algoliaを使う上で知っておきたい事
Algoliaを知らない人のために
codesandboxでの作例がここにあります。これでイメージを固めてください。
世界中の有名サイトで使われている全文検索エンジンです。
環境変数を使ってindexの更新を制御する
gatsybyでアルゴリアを使う時、gatsby-plugin-algolia
プラグインを使って、だいたいこんな感じで設定すると思うのですが、
const queries = require('./src/utils/algolia')
module.exports = {
plugins: [
{
resolve: `gatsby-plugin-algolia`,
options: {
appId: process.env.GATSBY_ALGOLIA_APPID,
apiKey: process.env.GATSBY_ALGOLIA_API_KEY,
indexName: process.env.GATSBY_ALGOLIA_INDEXNAME,
queries
}
}
]
}
これでは yarn build
する度にAlgoliaのindexを更新しようとしてしまいます。
投稿に更新がない時にindexの更新がかかってしまうのは無駄です。
そこでindexの更新を環境変数を使って制御する方法をお勧めします。 つまりこんな感じです。
const queries = require('./src/utils/algolia')
module.exports = {
plugins: [
...(process.env.GATSBY_ALGOLIA_UPDATE_INDEX === 'true' ? [
{
resolve: `gatsby-plugin-algolia`,
options: {
appId: process.env.GATSBY_ALGOLIA_APPID,
apiKey: process.env.GATSBY_ALGOLIA_API_KEY,
indexName: process.env.GATSBY_ALGOLIA_INDEXNAME,
queries
}
}
] : [])
]
}
GATSBY_ALGOLIA_UPDATE_INDEX = true yarn build
としないとindexが更新されないように
なり、Algoliaへの無駄なリクエストを防ぐ事ができます。
queriesで取得してくる結果にはidが必ず必要
queries
はこんな感じで自分は用意しているのですが、
const fetchAllPosts = `
{
allMarkdownRemark {
edges {
node {
id
fields {
slug
}
frontmatter {
title
category
subcategory
tags
createdOn
updatedOn
}
excerpt
}
}
}
}
`
const unnest = node => {
const { fields, frontmatter, ...rest } = node
return {
...fields,
...frontmatter,
...rest
}
}
const queries = [
{
query: fetchAllPosts,
transformer: ({ data }) =>
data.allMarkdownRemark.edges.map(edge => edge.node).map(unnest)
}
]
module.exports = queries
fetchAllPosts
のクエリで id
を書かなかったら、gatsby-plugin-algolia
が発行したクエリの結果を処理できずに以下のようなエラーが出ました。
Algolia: 1 queries to index
Algolia: query 0: executing query
⠹ onPostBuild
not finished onPostBuild - 0.317s
error Command failed with exit code 1.
本当はgatsbyレポーターがキャッチしてエラーより詳細なエラーを吐いてくれると思うのですが、そうなってなかったので この原因を調べるためにはコードを読むしかなかったです。
algoliaのindexでは必ずobjectIDが振られるようになってます。
コードを見てみたら理由が分かって、「id」を使って、indexに必要な「objectID」を生成していました。
const objects = (await transformer(result)).map((object) => ({
objectID: object.objectID || object.id,
...object,
}));
「id」もしくは「objectID」が必要って事ですが、allMarkdownRemark
には「id」しかないので「id」を取得するように書く必要があります。
Mailgunを使った自動メール送信機能を作る時に知っておきたい事
よくお問い合わせした時って、自動返信メールがきますよね? それをMailgunを使って実装する方法について軽くコツを紹介しておきます。
基本この記事を参考にして設定していけばいいです。
Mailgunの使い方に関してはこちらのQiitaの記事がわかりやすかったです。
ローカルで9000ポートにnetlify functionsサーバーを立てる方法
netlify functions
を使って send-contact-email
を実装をしていくのですがローカルでの動作確認でつまづくと思います。
こちらの記事では動作確認の時に
netlify functions:invoke send-contact-email --no-identity --payload '{"contactEmail" : "jenna@example.com", "contactName" : "Jenna", "message" : "hello world from a function!"}'
のように実行すればいいとあったのですが実際にやってみるとこんなエラーが出ます。
{
"name": "山田太郎",
"email": "yukihirop@example.com",
"subject": "ブログの記事で質問があります----",
"message": "テストメッセージ\nテストメッセージ"
}
$ netlify functions:invoke send-contact-email --port 9000 --no-identity --payload ./payload.json
ran into an error invoking your function
FetchError: request to http://localhost:9000/.netlify/functions/send-contact-email failed, reason: connect ECONNREFUSED 127.0.0.1:9000
at ClientRequest.<anonymous> (/Users/yukihirop/.nodenv/versions/12.7.0/lib/node_modules/netlify-cli/node_modules/node-fetch/lib/index.js:1455:11)
at ClientRequest.emit (events.js:203:13)
at Socket.socketErrorListener (_http_client.js:399:9)
at Socket.emit (events.js:203:13)
at emitErrorNT (internal/streams/destroy.js:91:8)
at emitErrorAndCloseNT (internal/streams/destroy.js:59:3)
at processTicksAndRejections (internal/process/task_queues.js:77:11) {
message: 'request to http://localhost:9000/.netlify/functions/send-contact-email failed, reason: connect ECONNREFUSED 127.0.0.1:9000',
type: 'system',
errno: 'ECONNREFUSED',
code: 'ECONNREFUSED'
}
それもそうで、send-contact-email
ファンクションを9000ポートで動かしてないからです。
動かし方に関しては書いてなかったので実際に動かし方を書いておきます。
netlify-lambda
を使います。
yarn -D add netlify-lambda
を実行して netlify-lambda
を入れます。
でインストールしたらこのようなスクリプトを書きます。
"scripts": {
"netlify:send-contact-email:dev": "netlify-lambda serve src/functions/send-contact-email"
}
名前が少し長いですが、名前はなんでもいいです。ポートの指定をしなくても9000ポートになってます。
自分は、src/functions/send-contact-email
ディレクトリにsend-contact-email
を作ってます。
$ yarn netlify:send-contact-email:dev
yarn run v1.19.0
$ netlify-lambda serve src/functions/send-contact-email
netlify-lambda: Starting server
Hash: e0f09b0eb79ec3dd76df
Version: webpack 4.43.0
Time: 5943ms
Built at: 2020-06-06 18:00:34
Asset Size Chunks Chunk Names
send-contact-email.js 1.11 MiB 0 [emitted] send-contact-email
Entrypoint send-contact-email = send-contact-email.js
[12] external "fs" 42 bytes {0} [built]
[13] external "path" 42 bytes {0} [built]
[23] external "crypto" 42 bytes {0} [built]
[37] /Users/yukihirop/JavaScriptProjects/blog/node_modules/mailgun-js/lib/attachment.js 1.36 KiB {0} [built]
[93] ./send-contact-email.js 2.02 KiB {0} [built]
[94] /Users/yukihirop/JavaScriptProjects/blog/node_modules/dotenv/lib/main.js 2.93 KiB {0} [built]
[95] /Users/yukihirop/JavaScriptProjects/blog/node_modules/mailgun-js/lib/mailgun.js 5.78 KiB {0} [built]
[96] /Users/yukihirop/JavaScriptProjects/blog/node_modules/tsscmp/lib/index.js 1.15 KiB {0} [built]
[98] /Users/yukihirop/JavaScriptProjects/blog/node_modules/mailgun-js/lib/request.js 10.7 KiB {0} [built]
[246] /Users/yukihirop/JavaScriptProjects/blog/node_modules/mailgun-js/lib/build.js 3.12 KiB {0} [built]
[250] /Users/yukihirop/JavaScriptProjects/blog/node_modules/mailgun-js/lib/schema.js 18.2 KiB {0} [built]
[251] ./md/index.js 1.95 KiB {0} [built]
[252] /Users/yukihirop/JavaScriptProjects/blog/node_modules/markdown-it/index.js 52 bytes {0} [built]
[307] /Users/yukihirop/JavaScriptProjects/blog/node_modules/markdown-it-br/index.js 1.8 KiB {0} [built]
[308] /Users/yukihirop/JavaScriptProjects/blog/node_modules/markdown-it-small/index.js 2.58 KiB {0} [built]
+ 295 hidden modules
Lambda server is listening on 9000
これで9000ポートにsend-contact-emailファンクションが動いているサーバーが立ち上がりました。
では先ほど実行できなかったコマンドを別のタブを開いて実行してみましょう。
ターミナルからお問い合わせをする
$ netlify functions:invoke send-contact-email --port 9000 --no-identity --payload ./payload.json
自動返信メッセージが正常に送信されました
この結果は正しく自動返信メールが送れたらそのように返すと自分で設定したのでそのように返ってきているわけですが、 成功してます。
画面からお問い合わせする
画面から問い合わせをする時は、localhost:8000
から localhost:9000
にPOSTリクエストを送ることになるので
何も設定しなければcorsの問題が発生して動きません。
const autoSend = await axios.post("http://localhost:9000/.netlify/functions/send-contact-email", { name, email, subject, message })
この状態で問い合わせしたらcorsの問題が発生して問い合わせに失敗します。
ではどうすればいいのかというと、proxyを使いましょう
と公式ドキュメントに書いてありました。しかもドンピシャでnetlify-functionsへのproxyの設定の仕方が書いてあります。まず必要なものをyarn addで入れて
yarn -D add http-proxy-middleware
const { createProxyMiddleware } = require("http-proxy-middleware")
module.exports = {
developMiddleware: app => {
app.use(
"/.netlify/functions/",
createProxyMiddleware({
target: "http://localhost:9000"
})
)
},
}
これで解決します。
Google reCAPTCHA (v3) を使う上で知っておきたい事
codeep/react-recaptcha-v3を使ってクライアントサイドの実装は一瞬で終わります。問題はサーバーサイドです。
まぁサーバーサイドなしでtokenが発行されたら良しとしてあるブログも多々ありますが、サーバーサイドでtokenをverifyして scoreを出してそれでロボットかどうかを判定する機能なのであまりよくないでしょう。
サーバーサイドの実装はやはり、netlify functions
を使って実装したが楽です。
実装例がfirebaseの例ですがgoogleblogの6.reCAPTCHAのレスポンスを検証するCloud Functionを作るにあったので紹介しておきます。
これに従ってやればいいです。
ただ一つ注意点があって、googleblogでは
exports.checkRecaptcha = functions.https.onRequest((req, res) => {
// ここに処理をかく
}
となってますが、netlify functionsのラムダ関数なので形式は以下の形式にしないといけません。
exports.handler = async (event) => {
// ここに処理をかく
}
Mailgunを使った自動送信メール機能の説明をした時に設定したようにローカルで動かすためのコマンドを用意しておいたがいいでしょう。
"scripts": {
"netlify:google-recaptcha-v3:dev": "netlify-lambda serve src/functions/google-recaptcha-v3"
}
以上です。
Iframelyを使う上で知っておきたい事
Even if you can’t give the moms in your life a hug today, I hope you can give them an extra thank you today. Thank you and Happy Mother’s Day to the woman who makes it all possible. Love you, @michelleobama. pic.twitter.com/E9ebxaSBpy
— Barack Obama (@BarackObama) May 10, 2020
Iframelyとはtwitterとかを埋め込むためのサービスですが、TypeScriptを使っている場合、 よく紹介されている方法が使えないので代わりの方法を紹介しておきます。
よく紹介されている方法|ブログに外部コンテンツを良い感じに埋め込むならIframelyがオススメ!|mono blog
ですがこの方法を使ってやると次のようなエラーが出てscriptタグでhttps://cdn.iframe.ly/embed.jsが読み込まれません。
TypeError: window.iframely.load is not a function
React.hooksを使ってこの問題を解決することができます。
要は、window.iframely
を使わないようにすればいいだけです。
import React, { useEffect } from 'react'
const IFRAMELY_URL = 'https://cdn.iframe.ly/embed.js'
const IFRAMELY_ID = 'iframely-embed-script'
// [ref]
// https://github.com/tterb/gatsby-plugin-disqus/blob/master/src/components/Disqus.jsx#L39-L52
const Iframely = () => {
useEffect(
() => {
const loadInstance = () => {
if (typeof window !== 'undefined' && window.document) {
const script = window.document.createElement('script')
const parent = window.document.body
script.async = true
script.src = IFRAMELY_URL
script.id = IFRAMELY_ID
parent.appendChild(script)
return script
}
}
const cleanInstance = () => {
const parent = window.document.body
const script = window.document.getElementById(IFRAMELY_ID)
if (script) {
parent.removeChild(script)
}
}
if (typeof window !== 'undefined' && window.document) {
loadInstance()
}
return () => {
cleanInstance()
}
},
[]
)
return <></>
}
export default Iframely
使い方は、iframelyを使いたい場所で <Iframely />
とするとbodyの中で<script async src="https://cdn.iframe.ly/embed.js" />
が設定されてIframelyを使う事ができるようになります。
以上です。
まとめ
-
Algoliaを使う上で知っておきたい事
- Algoliaのindex更新は環境変数で制御して無駄なリクエストを減らす方法を説明しました。
- onPostBuildでAlgoliaがexit 1を返したら、データにidがない事を疑ったがいいと説明しました。
-
Mailgunを使った自動メール送信機能を作る時に知っておきたい事
- ローカルホストで9000ポートにnetlify functionsサーバーを立てる方法を説明しました。
- ターミナルから自動メール送信機能を使う方法を説明しました。
-
画面から自動メール送信機能(お問い合わせ)を使う時に注意したい事を説明しました。
http-proxy-middleware
を使って9000ポートへproxyする設定が必要
-
Google reCAPTCHA(v3)を使う上で知っておきたい事
- firebaseだけどgoogleblogにやり方がある事を説明しました。
- netlify functionsなのでexportsする関数の形式の違いだけ注意点があると説明しました。
-
Iframelyを使う上で知っておきたい事
-
Gatsby x TypeScriptなブログではよく紹介されている方法は使えないと説明しました。
- 代わりの方法に関してコード付きで説明しました。
-
以上です。最後は4.まとめです。