RM-BLOG

IT系技術職のおっさんがIT技術とかライブとか日常とか雑多に語るブログです。* 本ブログに書かれている内容は個人の意見・感想であり、特定の組織に属するものではありません。/All opinions are my own.*

漫画サイトを作り直した話

はじめに

2020年の年末位にクローズした漫画サイトを、今回装いを新たに作りなおした。サイトはhttps://www.resign-threat.com/
背景としては、漫画そのものの描き直しが発端だったのだが(ここに書いてある)、合わせてサイト自体も全面刷新したので、今回そこに焦点をあててブログとしたい。つまり、この記事は基本的にはサイト作りの(技術的な)内容を取り扱うものである。漫画そのものの話は別途書きたいと思っている。
なお、基本的な漫画のコンセプトは変えておらず、「敵が暗号を喋る」というテーマと、「喋ってる暗号の解読に挑戦できる(するための材料を提供している)」という点は同じ。暇な人は是非暗号の解読に挑戦していただきたい!お待ちしてます。

構成

  • アプリケーションはNext.jsで、Vercelにdeployしている。一方画像ファイルはS3に置いて、Cloudflare経由で配信している。つまりCDNとしてVercelとCloudflareの2経路が存在する。これには一応自分なりの理由がある。後述。
  • 漫画の作成はClip Studioで作業し、そこで作った画像をS3にアップロードする一方で、アプリケーションの開発・修正はRemote Containerで作業し、ソースコードGithubにpushする。漫画とサイトで作業環境が異なる形である。これも後述。

普段の漫画の作成スタイル

  1. Clip Studioで漫画を描く
  2. この漫画作業フォルダはGoogle Driveの同期対象に設定されていて、.clipファイルとか.jpgファイルとかは自動でGoogle Driveに同期される
  3. 自作のコピーツール(コマンドプロンプト)で漫画サイト開発フォルダのほうにコピー
  4. 自作のアップロードツール(中身はpython)でS3にアップロード、あわせてCloudflareの対象ファイルURLのCacheをPurge。

  5. 1.~3.はWindowsマシン上で作業しているが、4.はVS CodeのRemote Container上で作業しているので、作成過程において作業環境が変わることになる。普段からClip StudioとVS Codeは両方開いておいて、タイミングで切り替えている感じ。ちなみに暗号作成もRemote Container上で作業している。

  6. 3.のツールは別になんてことはない、基本的には xcopy を実行するだけの簡易なコピーコマンド集である。Remote Container側の作業フォルダに、(Remote Containerから見るとホストOS側から)直接ファイルをコピーして持っていくためのツール。
  7. 4.のツールは、もともとはaws s3 cp するだけのLinux Shellだったのだが、CloudflareのCache Purgeしなきゃいけなくなったりとかで、もうちょっと柔軟に色々できる言語で操作がしたくなった。なんでも良かったんだが、Node.js(javascript)ばっかり使うのも若干飽きたので、Python使って書いてみた。boto3使ってS3にアップロードして、urllib.request使ってCloudflareのCacheをPurgeするだけの比較的シンプルなものだが、ちょっとしたPythoの実装経験にはなった気がする。こういうスクリプト言語の手軽さは慣れてしまえばどの言語も大体同じだろうな。
  8. 漫画作業フォルダのほうはGoogle Driveの同期対象になっているので、作成or更新とほぼ同タイミングで自動バックアップされるようになっているが、Remote ContainerのほうはGoogle Driveの同期対象ではない。これは、ソースコード系はGithubに全部置いてあるのでわざわざGoogle Driveの同期対象にするまでもないということと、同期対象にするとnode_modulesの中身とかの同期にやたらと負荷・時間がかかって作業に支障をきたした経験があったたため。漫画サイトに限った話ではないが、こういった背景があり、開発物はGoogle Driveの同期対象外にしている。

アプリケーション全般

  • アプリケーションはNext.jsで、Typescript+Tailwindcssで開発している。基本的な技術スタックはひなっちのおはっちのサイトとほぼ同じである。Firestoreなどのバックエンドがない分、こっちのほうがいくらかシンプルである。いずれにせよこの開発スタイルは個人的に分かりやすくて良い。今後も特に何もなければこのスタイルで開発していくと思う。
  • Next.jsのRecommendに従い、基本的にはコンテンツ系のページはSSGである。暗号やサブタイトルあてのところだけはどうしようもないのでAPIを用意していて、これはSSRだが、逆に言うとSSRはそこくらいで、他はすべてSSG。
  • 暗号化ツールに関しては、前のサイト作ったときに用意していたものがあったので、これを今回の運用にうまくはまるようにちょっと改造した。順番にshell叩いていけば基本的にはそのままコンテンツデータが生成されてWeb側の資産に適用できるようにしている。
  • キャッシュは、deployする度Vercelが勝手に消してくれるらしいので、それにお任せしている。というかむしろ自分から任意のタイミングで消す方法がわからない。恐らくここはManagedされており、開発者からは手が出せない部分なのだと思われる(そう思うことにする)実際、上述した通り、SSGをメインに使っている関係で、キャッシュの削除はそれで十分なのである。SSRAPIはdeployのタイミングで消えるが、アプリケーション依存の挙動なので問題なし。そもそもそんなに呼ばれる想定もない(残念だけどw)画像のほうはVercelを使ってないので自分で消す必要があるが、それもやり方は確立しているので問題なし。ここは後述する。
  • サブタイトルとか暗号解読用のチャレンジ窓などでreact-modalを使用している。基本的にほとんどこのGitHubのReadmeに書いてあるとおりの実装である。あまり凝ったことはしていない。思ったより簡単に導入できてちょっと拍子抜けだった。まあそれはそれで良いことなんだろうけど。
  • 暗号解読チャレンジにおいて、複数の暗号を相手にする場合に、react-swipeable-viewsを使ってスワイプできるようにした。同じようなライブラリがいくつかあるようで、いくつか試行したんだが、一番先にこれがそれっぽい実装にたどり着けたので、これを使っている。デモサイトではMaterial-UIのTabを使っているが、このためだけにMaterial-UIを導入するのもなんか嫌だったので、やめた。ただ、Material-UI自体には興味はあるので、そのうちどっかで使うかもしれない。
  • ひなっちのおはっちのサイトではなんかちょっと上手い具合に実装できなかったメニューについて、今回はサイドバーを実装してみた。基本的にはこれをまねた。ほぼこれそのまま。

画像関連

  • 画像もVercelから配信できればわかりやすかったんだが、以下理由により別ルート(Cloudflare)から配信することに決めた。2.の理由は大きいかもしれないw Zenが確かそういう形で実装されていたので(Cloudinary使ってた気がする)、自分なりにもうちょっと簡素な形で実現してみようと考えた。
    1. 漫画というサイトの性質上、画像の数がけっこう多くなりそうと予想されたが、そうなった場合に、あまりGithubで画像を大量に管理したくなかった
    2. 「アプリとは別に画像だけCloudflareから配信」というのをやってみたかった
  • 特に上の1.に関して、画像をGithubで管理してGithub ActionsでS3にdeployというのも、調べた感じでは出来そうではあったが、deployするたびに毎回リポジトリ内全てをdeployし直しになることを考えると、どうにもやはり抵抗があった。例えば第02話の更新だけしたいというときに、第01話も第02話もキャラクター紹介もその他諸々全部含めて再deploy、っていう挙動は、個人的にはどう考えてもおかしい。個人的な好奇心でちょっとやってみたい気はするが、少なくともこの漫画サイトに関しては選択肢にはなり得ないと思ってやめた。
  • このため、画像はGitによる資産管理の対象外になっている。S3にアップロードするツールを個別に作成していて(このツールもGithubにあげている)、このツールが「最終更新日時以降の画像」を拾うように実装している。これで疑似的に画像の構成管理を実現している。また、このツールは、S3へのPUTにあわせて、CloudflareのCache Purgeも行う。(File指定パージ)これによって画像に修正が入った場合に最新画像を配信するように制御する。なお、File指定パージは30ファイルまでしか指定できないので、30ファイル以上あった場合はこbのリクエストを繰り返すことになる(これが面倒くさい。。)
  • S3のバケットポリシーにより、Cloudflare経由以外 及び サイトドメイン www.resign-threat.com 以外からの直接アクセスは禁止している。要するに画像への直リンを基本防止している。前者はCloudflare経由で配信する場合の常套手段だが、後者は正直オマケみたいなもので、技術的好奇心によりやってみたかったという理由のほうが大きい。漫画画像の配信URLが非常に予想されやすいものになっているので、画像のURLにだけ直接アクセスしようと思えば、サイト更新を待たずとも先んじて見れるという状況が発生し得る。これを防止したかった。実際にはReferer使ってるだけなのでちょっと手を加えれば外からも見れるようだが、まあとりあえず実現できたので一旦満足している。なおステージング環境のバケットの配信ドメインは、CloudflareのFirewallルールに我が家のグローバルIPを指定しており、実質我が家以外から見ることはできない。

残課題・懸念点等

  • Staging環境を個別に用意していないところ。開発用のブランチにpushするたびにVercelが勝手にランダムURLでサイト用意してくれるので、もうこれでいいや(誰にも予想され得ることはあるまい)と思うことにした。本当はひなっちのサイトみたいにしてサイト自体を外から完全隠蔽したかったのだが、上述した通り、肝心のステージング用バケットの漫画画像はCloudflareで防御していて他の人には見れないし、サイトドメイン自体を隠蔽するとなると色々手間かかりそうなのでやめた。ただ、ProductionはCloudflareをDNSに使っているのに対し、Staging環境はVercelに直接アクセスして使っており、そういう意味ではStagingとProductionで構成が異なるのは事実である。12 Factor Appに反しているのでここは個人的に気持ち悪いところで、可能ならなんとかしたい。
  • 上と関係しているのだが、deploy(で作られたサイト)の履歴をVercelがどれほど保持してくれるのか不明。調べても特に載ってない。(気がする)延々過去の履歴をずーっと持つ必要もないと思っていて、実際変に入り口を増やすだけならむしろ自動で消していってほしいという思いの方が強い。DeployごとのサイトはPublicなので誰でも見れる(アクセスできる)のも、この気持ち悪さを増長している。もしくは、ブランチ消せば合わせて自動で消えてくれるのか?その辺調査が済んでいない。ただ、完全なる個人開発物なので、基本的に分ランチは2つしか切っておらず(mainとdev)、devのほうをポンポン更新しまくっている関係で、devブランチを削除することは考えていない。もしかしたら発想が違うのかもしれないが。。。
  • 今はまだ第02話くらいの段階なので気にしていないが、これが第10話・第20話…と増えてくると、ビルドの時間が長引くのではないかという懸念がある。基本的にほぼすべてのコンテンツページはSSGなので、これは下手すると死活問題になり得る。実際物語を進めて行かないとどれくらいかかるのか予想がつかないのが恐れているところ。まあ1deployに対して45分も使わせてくれるらしいし、現状30秒~1分くらいで終わってるので、気にするほどでもないかもしれないが、仮に長引いたときにどう対処したらいいものか検討がついていない。
  • 知らなかったのだが、VercelってどうやらCloudflareにDNSプロキシできるらしい。ちょっと前に調べたときはそんなこと書いてなかったというかむしろ「プロキシするな」と書かれていた記憶があるのだが(その記事を見つけられない)、こうやって書かれてる以上はできるんだろうね。勿論、ここで重要なのはDNSの隠蔽の話ではなくてCDNキャッシュの話である。CloudflareにDNSプロキシするということは、Cloudflareにキャッシュされるということだが、それがオリジンとしてのVercelのキャッシュ機能の無力化(無視)を意味していないはず。今は上に書いた通りで、Vercelにdeployするたびにキャッシュが消える、で運用上満足しているので、Cloudflareにキャッシュされることで余計なキャッシュが生まれるため、むしろDNSプロキシが邪魔になる可能性がある。しかし出来るんなら試してみたい。まだなんも試してないが。これを検証するのをどこかでやりたいなあと思っていたりする。面倒くさいからやらないかもしれないがw

余談

  • 使うプラットフォームとして、Amplifyも最初は考えたんだが、ビルドにめちゃ時間がかかって、萎えてやめた。vercelだと30秒~1分くらいで終わるやつが、Amplifyだと12分くらいかかる。ビルドっていうか、Amplifyはリリースに際して毎回裏側の(EC2とか?の)リソースを用意(作り直し?)している節があって、それが時間の大半をかけているように見えた。どっちにせよ待ってるのが馬鹿らしくなったので、不採用。
  • Cloudflare Pagesも考えたのだが、現時点ではまだSSGにしか対応していないらしく、SSRが使えないという点で、(その時点ではSSRを使うことを明確に決めていたわけではなかったが)選択肢が狭まると考え、採用を見送った。

おわりに

この記事はサイト作りの話に限定しているが、個人的にはここにおいて、「漫画を描く」という趣味と、「サイト作り」という趣味を両方兼ねられているのはとても素晴らしいことで、我ながらオモチャを手に入れた(作った)な、と思っている。また、この過程を経て、漫画を描くこと自体が面白くなったのも良いこと。まあ別にどっちも大して上手ではないんだが、そこは重要ではない。俺が楽しめていればあとのことは基本的に後回しで良いのだ。勿論作りながら&描きながらスキルアップしたいとは思っているが、それは二次的な話で良い(そっちを主体にしてしまうと謎の「義務感」が生まれるため少なからずストレスになることが予想される)。まずは楽しむこと。それでよいと考える。
一方で、こと「サイト作り」の話に限定すると、「漫画を載せるためのプラットフォーム」という都合、基本的にもう役目が終わってしまっているのは、個人的には少し残念なところだ。漫画のストーリーが進むたびに、その更新分に関してコンテンツデータに変更を加えて反映していくための「修正」は継続的に発生するが、これは最早サイト内の一部であり、大体的な改修作業には至らない。まあ考えれば機能追加とかのネタがないわけでもないんだが(上に書いた「残課題」の対応とか)、一旦現時点では出来る範囲の開発は終えてしまっていて、落ち着かざるを得ないというのがちょっと寂しい。そういう意味では、サイト作りのアイディアや練習としては、他に用意するほうが適切なんだろうなあと思っている。そっちはそっちで追々何かを考えていきたい。