GO TO TOPPAGE

Hugo と Firebase Hosting でブログを構築するまで

このブログは meta タグに埋め込まれている通り、Hugo を使って作られている。

The world’s fastest framework for building websites |...

The world’s fastest framework...
https/gohugo.io

Hugo はかなりメジャーな静的サイトジェネレーターの1つであるので、たいていのことは調べれば情報が出てくるだろう。このブログもほとんどは公式ドキュメントにある基本的な機能で構成されている。この記事では Hugo を Firebase Hosting で運用するいくつかの Tips と個人的な課題を残す。

Hugo

設定ファイルの切り替え

開発環境 (development)、開発環境 (staging)、本番環境 (production) でそれぞれ設定ファイルを切り替えることができる。例えば、Google Analytics のタグコードを本番環境のみに埋め込むなどの状況で役立つ。

Configure Hugo | Hugo

How to configure your Hugo...
https/gohugo.io

ちなみに、Google Analytics については、これから導入するのであれば Google Analytics 4 一択だが1、Hugo 的には Universal Analytics2 にも対応している。

Internal templates | Hugo

Hugo ships with a group of...
https/gohugo.io

aタグ

通常のマークダウンリンクをビルドすると、aタグに target="_blank" を付けることができないが3、Render Hooks という機能を使うことで、マークダウンのレンダリング機能を上書きすることができる。

Markdown render hooks | Hugo

Render Hooks allow custom...
https/gohugo.io

target="_blank" を付ける例は上記ドキュメントに例として紹介されている。

1<a href="{{ .Destination | safeURL }}"{{ with .Title}} title="{{ . }}"{{ end }}{{ if strings.HasPrefix .Destination "http" }} target="_blank" rel="noopener"{{ end }}>{{ .Text | safeHTML }}</a>

しかし、この例では、リンクが http で始まらない場合、期待したように動かない。例えば、refreflink といった shortcode と組み合わせた場合などにも機能させるには修正が必要である。

現状、そこまでこのブログでも対応しきれていないので、追々対応したい課題である。Markdown のパースと shortcode の展開順序を考えるに、一筋縄ではいかなさそう・・・。

こういうときに、Go が書けると良かったになぁと思わないこともないが、同時に、このためだけにわざわざ Go に手を出すのもなぁと思う当たり、歳を取ってしまったと実感する・・・

コメント機能

現状、各記事にコメント機能を実装していない。このブログを開発中の段階では、しばらくコメントは Twitter で受け付ければ問題なかろうと考えていたが、数ヶ月の間にイーロン・マスク体制の Twitter に様変わりし、大きく状況が変わってしまった。

コメント機能を静的サイトに付けるには Disqus が有名だろうか。Hugo ではこれ以外にもいくつかのサポートがなされているらしい。

Comments | Hugo

Hugo ships with an internal...
https/gohugo.io

コメントでのやりとりも含めて1つのコンテンツと捉えると、Disqus のようなサービスを利用することはこのブログの方針に反する。

大事なものはきちんと手の届くところに置いておきたい。
https://tsukasa.oomo.to/2022/10/31/open-a-new-blog/

そうなると、セルフホストで同様なものを探すか作るかしないといけないが、せっかくほぼ Firebase Hosting で運用が完結しているのに勿体ないなぁという気持ちが芽生える。コメント機能は様子を見つつ、もう少し考える事とする4

全文検索機能

全文検索機能もあれば便利だなと思うけども、未対応である。

Search tools | Hugo

See some of the open-source...
https/gohugo.io

幸い、既に Google のインデックスには含まれているみたいなので、優先度は低い。どうしても必要な時はサイト検索を使えば良いだろう・・・。

Firebase Hosting

Firebase Hosting

Firebase Hosting provides fast...
https/firebase.google.com

Hugo でのビルド成果物を Firebase Hosting にデプロイし、運用している。公式ドキュメントにも(これだけ見てもどうしようもできない程度の)言及がある。

Host on Firebase | Hugo

You can use Firebase's free...
https/gohugo.io

基本的に無料で Firebase Hosting の利用は出来るが、Cloud Functions for Firebase を利用するためには有料プランに登録する必要がある。詳細は料金プランにて比較できる。

Firebase Pricing

Firebase を無料で開始し、世界中の何百万人ものユー...
https/firebase.google.com

従量課金制プランになると、最悪ケース、悪質な攻撃の対象となるとクラウド破産の恐れがある。最低でも、月のアクセス推定量から予算とアラートを厳しめに設定しておく必要はあるだろう。ただし、予算とアラートを設定したところで、リソースや API の使用量は制限されないの気休め程度にしかならない5

Cloud Functions

元々の Cloud Functions には Python ランタイムが存在するが、残念ながら Cloud Functions for Firebase は Node.js ランタイムにしか対応していない6。私は JavaScript には不慣れで、何か作ろうとする度に Google で(恐らく)基本的な文法を検索している。

これでは少し複雑な機能を開発するには効率が悪く厳しい・・・。ということで、GCP の Cloud Functions も利用している。

ブログカードの表示

具体例として、The Open Graph protocol (OGP) タグの取得が挙げられる。Hugo は静的サイトジェネレーターという性質のため、下のようなブログカードを表示するために、OGP タグをビルド時なのか、デプロイ時なのか、表示時なのか、どこかで取得してくる必要がある。

株式会社AlphaImpact

人工知能で世界に新しい衝撃を。競馬予想人工知能をはじめとし、...
https/alphaimpact.co.jp

ブログカードに限らず、様々なメディアカードを表示するサービスとして Embedly というのを見つけたが、無料プランだと Embedly のブランド表示が付与されてしまうため、若干見た目がださい。これを消すためには、有料プランを検討する必要があるが、ただブログカードを表示するためだけに、毎月 $9 も支払うのは正直無駄のように感じる。

Cards are a clean, responsive, and shareable way to...

Use Cards to include richer...
https/embed.ly

そこで、OGP タグを取得するだけのサーバーレスアプリをぱぱっと作成して、Cloud Functions にデプロイした。実装のメイン部は下記のようになった。

 1ogp = {}
 2for meta in soup.select(r"head > meta[property^=og\:]"):
 3    _content = meta.get('content', '').rstrip()
 4    # ex.) 'og:title' -> (og, title) -> 'title'
 5    # ex.) 'og:image:height' -> (og, image, height) -> 'image:height'
 6    _property = ':'.join(meta.get('property', '').split(':')[1:])
 7    ogp[_property] = _content
 8else:
 9    # OGPが設定されていないとき、その他のタグから同様の情報を取得する
10    if (title := soup.select_one(r"head > title")):
11        title = title.string.strip()
12        ogp['title'] = title
13        ogp['site_name'] = title
14    if (description := soup.select_one(r"head > meta[name=description]")):
15        description = description.get('content', '').strip()
16        ogp['description'] = description
17    ogp['url'] = url

対象ページを取得したら、Beautiful Soup 4meta タグを解析し、OGP タグを抽出している。

Cloud Armor

Cloud Functions (for Firebase) は関数をデプロイすると、エンドポイントさえ知っていれば、デフォルトで誰でも関数を叩けてしまう。アクセス制限を導入するために Cloud Armor を導入した。

Cloud Armor のネットワーク セキュリティ | Google Cloud

Google Cloud Armor は DDoS 攻撃やア...
https/cloud.google.com

固定IPを持つサーバーを保有しているため、そのIPからのみアクセス可能なルールを現状運用しているが、他にも日本国内のみアクセス可能といったルール (origin.region_code == 'JP') の設定も可能である。


他にもよりクオリティの高い関連記事の表示などにも手を付けられると思うが、まだそこまで手が回っていない。必要最低限の機能は揃っているので、今は、そもそものコンテンツ充実が最優先であった。


  1. https://support.google.com/analytics/answer/10089681 ↩︎

  2. https://support.google.com/analytics/answer/11583528 ↩︎

  3. v0.60 より前の Hugo で採用されていた Blackfriday パーサーなら設定ファイルから対応できた ↩︎

  4. 現在のところ、確実に私に連絡を取りたい人はメールを送って欲しい ↩︎

  5. https://cloud.google.com/billing/docs/how-to/budgets ↩︎

  6. より厳密には、Firebase CLI からのデプロイに対応しているのが JavaScript or TypeScript のみである ↩︎

- OMOTO Tsukasa -

機械学習をバックグラウンドに持つソフトウェアエンジニア。(株)AlphaImpact 所属。LightGBM の(非アクティブ)コミッターの1人。自他共に認める、何でも屋。

#Blog

#Hugo

#Firebase

#GCP