2019/06/10
お久しぶりです。
以前、Nuxt.jsとFirebaseでchocottoというTwitterでお菓子と一緒にメッセージを送れるサービスを作ったG4RDSです。
先週、二十歳を迎えて成人しました🎉
今は高専五年で、編入試験を受けている真っ只中です。
適度にお酒を入れて、今後も個人開発頑張っていこうと思います!
さて、今回作ったWebサービスは「コツコツ忍者」というWebアプリです。
昔ばなしに、忍者は跳躍力向上のために毎日成長していく小さな木を飛び続けた、というお話があるのをご存知ですか?
ある能力を向上させたいのであれば毎日継続してやり続けることが大事である、ということですね。
自分が好きなことは続けられますが、好きではないけど上達したいことは続かないからなかなか上達しません。
そんな「上達したいけど続けられない」ことがある人のために、毎日やったことを記録できるWebアプリを作りました。
PWAなのでデバイスにインストールできるため、アプリを開くハードルも下がって毎日記録できるかなーと思います。
毎日やりたいことを習慣づけるWebアプリ「コツコツ忍者」をリリースしました🎉
— ジフォ (@G4RDSjp) June 10, 2019
記録を付けると、いままでやってきた量や継続日数の他、やった日のカレンダーも表示してくれます🗓
やってきたことを可視化してモチベーションアップしませんか?https://t.co/Fpb0Egn1Ed#コツコツ忍者
この記事では、コツコツ忍者で使った技術を紹介したいと思います。
コツコツ忍者は、毎日のやった記録を簡単に付けられるWebアプリです。
一日にやる量とやる曜日を決めて「やること」を作ったら、あとはやったその日に「今日もやった」ボタンを押すだけで、何日連続でやっているかや通算でどのくらいやったかなどのデータを計算してくれます。毎日やることに慣れてきたら、忍者の小さな木ように量を増やしてみるのもおすすめです。
今からでも腕立て腹筋を毎日やって夏の準備をしたいなどの毎日やり続けたいことがある方、コツコツ忍者ですぐに始めましょう!
https://ninja.g4rds.dev
コツコツ忍者はどういった技術に支えられているか、フロントエンドバックエンドともに紹介します。
コツコツ忍者はNuxt.jsで作られています。
今回のようなOGPを設定する必要があるWebサービスの場合、SSRは必須となってくるためVue.jsではなくNuxt.jsを選ぶ必要があります。
静的ページ生成機能もすごく良いので、ブログとか作るときにもおすすめです。(Vue系だけでもいろんな選択肢があるので色々比較してみてください。Gridsomeとか。)
コツコツ忍者はバックエンドにFirebaseを使っています。
NuxtのホスティングはGAEを使ってます。
公式ドキュメントではF2インスタンスが推奨されていますが、無料枠に収めたいのでF1にしてます。
CircleCIは、ビルド・デプロイを自動でやってくれるCI/CDサービスです。
連携したGitHubリポジトリにコミットをプッシュすると、CircleCIでコンテナが立ち上がり、事前に定めておいた手順に沿ってビルド・デプロイしてくれます。
GAEへのデプロイは数分かかるので、masterにプッシュしてあげるだけで裏で勝手に更新してくれるというのは非常に便利で快適です。
セットアップの数分を使うか、何度も行うビルド・デプロイ作業に時間を費やすか。
コマンドでデプロイできるやつは簡単にセットアップできるので、おすすめです。しかも無料。
Tailwind CSSは先日ついにv1.0になりましたが破壊的変更が入っているため、コツコツ忍者ではアップデートせずにそのままv0.7を使っています。
TailwindはユーティリティファーストなCSSフレームワークで、コンポーネントは入っていません。
一度しか使わないクラスを作るのではなく、直接マークアップにスタイルを書くように用意されたクラスを追加することで、レスポンシブデザインを実装できるのが強みです。
ドキュメントも豊富で使いやすいので、英語が読めなくても何となく理解できると思います。
クライアント側でOGP画像を生成するときに使っています。
値上げするよーの脅しにつられてProに課金してしまったので、コツコツ忍者にはFontAwesome Pro限定のアイコンがちりばめられています。
便利ですし、さらなるアイコンタイプ(duotone)の追加も予定されていますので、これからもお世話になります。
unDrawはおしゃれなイラストを全部無料で商用利用できるサイトです。
イラストはコンテンツの量増しになるので、寂しく感じたら使うようにしてます。
コツコツ忍者では、やることを完了する度に生成される画像が異なるため、一つのページに複数のOGP画像を割り当てる必要があります。
Twitterなどのクローラは同じURLに対しては一週間ほど画像をキャッシュするため、違うURLにする必要があります。
そこで、やることの固有IDと、画像を生成した時のタイムスタンプを組み合わせて、それぞれの画像に一意なIDを割り振りました。
html2canvasで生成した画像をCloud Storageにアップロードする際、ファイル名を「YOyGuKC5gNdCr4NtW5oj_1560067092042.png」などのようにしてアップロードします。
画像のダウンロードURLを取得して、Firestoreのやることのドキュメントに、タイムスタンプからURLを取得できるようなマップを入れておきます。
{
"1560067092042": "https://firebasestorage.googleapis.com/hogehoge",
...
}
シェアするURLのクエリにタイムスタンプを設定しておけば、Twitterから「ninja.g4rds.dev/tasks/YOyGuKC5gNdCr4NtW5oj/?ogp=1560067092042」にアクセスされるので、NuxtのasyncDataでクエリのタイムスタンプを使ってドキュメントから画像URLを取ってきて、metaのogp:imageにそのURLを設定することで、ツイートによって違う画像が表示されるようにしています。
追記 (19-06-12 00:08)
ここで紹介している方法は画像をクライアントサイドで生成し、サーバーにアップロードするものです。
この場合、悪意のある第二者によって不正な画像をアップロードしOGP画像として適用することが可能となってしまいます。(Cloud Storageへアップロードするデータを置き換える、ドキュメントに登録する画像URLを書き換えるなど。)
これを良しとしない場合はサーバーサイドで画像の生成、保存、設定まで全て行う必要があります。
前回開発したchocottoはサーバーサイドでレンダリングしていますので、詳しくはchocottoの開発記事をご覧ください。
SSRするNuxtではFirebase Authenticationで気を付けなければならないポイントがあります。
今度別の記事に詳しくまとめようと思いますが、この記事にはコツコツ忍者のログインフローを簡単に書いておこうと思います。
ポイントとなるのはLocalStorageにサインを置いておくことです。(あ、サインはわかればなんでもいいです。{"loggingin":"true"}とか。)
/loginのmountedでLocalStorageを確認し、サインがあったら5の処理を行います。
getRedirectResultの処理に数秒かかる場合が多いので、「ログイン処理中だから待ってねー」と表示しなくてはなりません。
プロバイダーからリダイレクトしてきたとき以外は表示したくないので、それがわかるようにサインを残しておくということです。
また、コードがどこで実行されるのかということにも気を付ける必要があります。
LocalStorageはクライアントサイドでしか利用できません。
そのため、ログイン処理はすべてクライアントで実行するようにします。
created、asyncData、fetchはNuxtクライアントがルーティングしたとき以外はサーバーサイドで実行されるので、mountedで処理を行いました。
自分がやってきた記録が数字とビジュアルで分かるというのはモチベーション維持にすごく効果的だと思います。
「毎日何かを勉強する」など、毎日継続してやっていることや、やりたいことがあったら、使っていただけると嬉しいです。
https://ninja.g4rds.dev
これからはしばらくFlutterを触ってみようかなと思います。
個人開発でWebサービスを収益化するには広告くらいしかなく、他のハードルが高いのに辛さを感じていましたが、アプリであれば簡単にサブスクでさえ作れるのが魅力的です。
Flutterは一つのソースでiOSもAndroidもビルドできるのがすごく良いですよね。
何か作ったらまた紹介させてください!
さて、余談ですが、今週の水木土に幕張メッセで開催されるAWS Summitに僕も参加する予定です。
初めてこういうイベントに行くのですごく楽しみです。
Twitterで会場の雰囲気とか感想とかつぶやこうと思いますので、よかったらフォローしてください。