Flutter Web、いいですよね。2021年5月にはついに安定(していない)版も出てきてますます使いやすくなってきていると思います。
適当なブログ/ポートフォリオサイトを作りたい
たまにWebサイトを作りたい、と考えることがあると思います。Flutter Webであれば静的ホストできるので、Firebase HostingやGithub Pagesにアップすればすぐに作れるのですが、記事や作品など適宜更新したいコンテンツがあるといちいちコミット・デプロイしないといけないのが面倒というのがあります。またコンテンツが増えてくると管理が煩雑になる&再利用の観点からデータ部とソースコード部を分離して考えたいという欲求も出てきました。
そこでまずデータの分離に役立つのがheadless CMSです。
Headless CMSとは
まずCMSとは「Contents Management System」の略で、コンテンツをデータベースとして管理しておき必要に応じていい感じにWebサイトを生成してくれる便利なやつです。代表的なものとしてはWordPressが挙げられます。
その中でもheadless CMSというのはフロントエンド機能を持たず、コンテンツ取得等のAPIのみを提供するシステムになります。WordPressでは基本テンプレートからページを選択する必要がありましたが、headless CMSであればNext/Nuxt/PHP/Ruby/Mobileなど好みのフロントでシステムを構築することが可能になります。
有名なものではContentfulやmicroCMS、Strapiなどが挙げられます。
Jamstack
しかしheadless CMSを使うには一つ注意点があります。
headless CMSにはAPI経由でデータを取得するためページを動かすにはAPIキーが必要になります。ということは、ページの生成側でAPIキーを持たなければいけないのです。PHPなどサーバ側で生成するものであれば問題ないのですが、静的ホストされたサイトはどうしてもクライアント側で処理する必要があるため配信ファイル側にAPIキーが存在する状態になってしまいます。さすがにAPIキーを晒してしまうのは構造上よくありません。
この問題を解決するのがJamstackになります。
JamstackはWebサイト配信についてのアーキテクチャで、事前にHTML生成(pre-rendering)を行うことで速く安全でスケーラブルな配信を行うことができるというものです。
上図のように開発者がコンテンツを更新すると更新通知(webhook等)によりCIが回り、その時点でのデータとコードから静的ページをビルドしてデプロイします。サーバに乗った時点ですでにデータを持っているためAPIアクセスの必要がなく、先程のAPIキーの問題を解決できていることが分かります。
またコンテンツの更新ごとにCIを回しデプロイするため、リアルタイムは無理にせよ常にほぼ最新のコンテンツ状態にすることができます。
Jamstackを使ったフロント開発では軽量なGatsbyが有名だと思います。
しかし待てよ...
Flutter WebでもJamstackが使えるのでは!?
Flutter+microCMSでの構成例
ということでFlutter Webを使った構成例が以下になります。headless CMSにはmicroCMSを採用しています。これは一例なのでどのサービスでも構築可能だと思います。
構成は前の図を使うサービスに置き換えただけのものになります。FlutterでJamstackを使うにあたってはCIからCMSのデータを取得する仕組み部分が必要になるのですが、microCMSを使う場合はstatic_micro_cmsというパッケージを作ったのでこれを追加すれば使えます(型生成までできるよ!)。実際必要なのはコマンドでデータ全部取ってくるだけなので他のサービスで使いたい場合もさほど困らないと思います。
作ってみる
では簡単に作る流れを見ていきましょう。
microCMSの設定
まずmicroCMSのホームページからログイン/新規登録をし、サービスを作成します。
サービスができたら新たにコンテンツ(API)を作成し、適当にデータを用意します。
次いでに型生成に必要なのでAPIの[API設定]->[APIスキーマ]->[この設定をエクスポートする]からスキーマのjsonをダウンロードしておきましょう。
Flutter側の準備
続いてflutterプロジェクトの準備ですが、pubspec.yamlを以下のように書きます。
#...
dev_dependencies:
flutter_test:
sdk: flutter
static_micro_cms: 0.2.0
static_micro_cms:
baseUrl: "https://[your-service-id].microcms.io/api/v1"
apis:
- endpoint: profile
type: object
schema: schema/api-profile-20211122080708.json
- endpoint: news
type: list
schema: schema/api-news-20211121223418.json
baseUrlは自分のサービスのものにしてください。schemaは先程ダウンロードしたものなので、適当な場所においておきましょう(例では./schema/以下に入れてます)。
baseUrlはAPIプレビュー辺りから見られると思う
また、CIや手元でデータを取得したいときにはAPIキーが必要なので.envにキーを書いておきます
API_KEY=[your api key]
ここまでできたらflutter pub get
後にflutter pub run static_micro_cms
を呼ぶことで
types.microcms.g.dart
datastore.microcms.g.dart
の2つのファイルが自動生成されます。datastoreの方にデータがjsonで入っているわけですね。
またソースをGitHub等に上げる場合は適切にファイルを除外することが必要です。
*.microcms.g.dart
.env
# お好みで
# schema/
schemaは型生成に必要なので含めていますが、公開したくない場合にはCIを回す際に注入するのでも良いと思います。
CIとデプロイ
最後にfirebase hostingとGitHub Actionsを構築します。
一度GitHubに接続しておいて、上記事のようにfirebase init hosting:github
を実行することで自動的にGitHub Actionsのyamlを生成してくれます。
.env
の生成とCMSからの情報の取得の処理を追加した一例は以下のようになります。
on:
push:
tags:
- 'v*'
jobs:
build-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Setup Flutter
uses: subosito/flutter-action@v1
- name: Install
run: flutter pub get
- name: Create .env file
env:
MICRO_CMS_API_KEY: ${{ secrets.MICRO_CMS_API_KEY }}
run: echo API_KEY=$MICRO_CMS_API_KEY > .env
- name: Fetch api data
run: flutter pub run static_micro_cms
- name: Build
run: flutter build web
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: '${{ secrets.GITHUB_TOKEN }}'
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_FASTRIVER_DEV_MICRO }}'
projectId: fastriver-dev-micro
channelId: live
バージョンタグを切ると.env
を生成、データを取得、ビルド、デプロイを行います。
microCMSからのwebhookの設定
microCMSはGitHub Actionsへのwebhookに対応しているのでその設定を行います。やり方は下の記事を参照してください。
トリガーイベントの名前は後で使うので適当で構いません。
次にwebhook検知時に動かしたいワークフロー(今回はmain.yaml)のon
部分に以下を追記します。
on:
repository_dispatch:
types: [update_works]
#...
update_works
の部分は先に決めたトリガーイベントの名前です。配列なので多分複数指定可能になっています。
これでJamstackの環境が整いました! コードの更新/コンテンツの更新ごとにCIが回りサイトが更新されるようになっていると思います。
課題点
Flutter WebでmicroCMS・Jamstackを使う場合には今の所課題点がいくつかあります。
RichTextの表示
headless CMSは記事の本文などにRichTextというHTML形式のテキストを利用しています。これにより見出しや画像など記事の表現力が高まるのですがFlutter Webは現状(Webのくせに)HTMLの表現が苦手です。easy_web_view
やwebviewx
などWeb対応のWebViewパッケージはいくつかあるのですが、スクロールを奪われたり縦横の大きさが固定だったりと実用としては難しい点があります。
Flutter Webのiframe周りの制約が大きいのが原因か
CIの時間
小規模なサイトですがCIを一度回すのに3分弱かかっています。Flutter SDKのセットアップに時間がかかっているようなのですがもう少し短くしたいです...
終わりに
課題はいくつかありますがheadless CMSのフロントにFlutter(Web)を利用する選択肢がとれる、というのは素晴らしいことと思います(CSSから逃げられるので)。Jamstackについてはかなり環境が整っているのでまだ伸びていく分野かなと思います。
ちなみにこの記事の通りに作ったサイトが以下のものになります。ありがとうございました。