🍊

Nuxt 3 + AWS + GitHub(コンテンツ管理) で技術ブログをつくった

2022/07/28に公開約7,600字

オリジナルのブログシステムをゼロから作ってみたのでご紹介申し上げたい…という記事です。

https://mirumi.tech

いわゆる雑記ブログは長いことやっているのですが、自分がこのあとも 5 年 ~ 10 年という単位で継続的に記事を書き続けていけるためには全ての仕組みが自分向けに整備されていないとモチベーションが消えていくな、と思いました。今回はその実験の第一弾として技術ブログをつくってみたという運びです。

言うまでもなく Zenn で記事を書いた体験がきっかけになっているところがあり、それを思うと安易なリリースおよびその自己紹介は Zenn への尊重心を欠くことになってしまうのでとても悩みました。ここは自分なりに色々考えてみた部分でもあるので、あとでちょっだけ触れさせてください。

つくったもの

全体像はこんな感じ。

my-great-blog-system

ユーザー(=僕)がやるのは「どこかしらのリポジトリにプッシュする」のみです。もちろん記事の中身も書いたらコミットするだけでリリースまですぐに完了します。

そういうわけで関連するリポジトリが全部で 4 つもあります。
主には下記の 2 つで、それぞれ Nuxt 3 用とその他サーバー側やインフラ類諸々です:

https://github.com/mirumirumi/mirumi-tech-frontend

https://github.com/mirumirumi/mirumi-tech-backend

要は「記事を書いてコミットしたらいい感じになる!」ということでございます。

Nuxt 3 is どう?

Nuxt 3 はまだベータ版なので評価がとても難しいです。

いま感じているツラい点はほとんど一時的なものである…と思いたいところですが、issue を立ててみたり discussion に参加してみたりと色々やってみた感じ、すぐに全部がよろしくなる(※僕にとって)わけではなそうな気配を感じました。

なのでなるべくそれらは除外しつつ、気づいたところをいくつかまとめておきたいです。

🙂:自動インポート系

この次に「自動で色々決まるのはイヤだ」みたいな項目が登場するのですが、程度によっては「ほぼ毎回使うものはいちいち書かなくてもよくない?」と思ってしまうのもまた人間であり、今回に関しては僕にとってはよい方に転びました。

下記のような内容は基本的にすべて書かなくてよくなっています。

import { ref, onMounted, watch } from "vue"          // いらない
import { useState } from "nuxt"                      // いらない
import MyGreatComponent from "@/components/TKG.vue"  // いらない

// do something...

使わないものはコメントアウトしたりせずにすぐに消したいタイプなので(プログラミングじゃなくて普段の生活も)、これは単純に便利さ向上で嬉しいポイントでした。

😑:自動決定されるもの系

Nuxt は 3 でより色々なものが自動で決定されるようになったと思います。

  • テンプレート内で使うコンポーネント名がディレクトリ区切りルール
  • runtimeConfig の値取得パス
  • (ページルーティング)

ページルーティングは近頃のスタンダードさ的にもいいと思いますし、そもそも「フォルダで区切って HTML を表示します」という古来のウェブの仕組みとしても踏襲されている感があります。これは納得しています。

しかしその他の項目は「ディレクトリやオブジェクトの構造で強制的に決まる」という挙動で、特にディレクトリが関わるものは「実装をコード以外で表現する」というのがどうしても苦手です。

フレームワークを使う以上はそのお作法に従うべきなのはわかるのですが、このあたりはかなり好みが出る場所だと予想されるのでもう少し選択の幅がほしいところです。実際にディスカッションの項目もいくつか存在しています:

みなさんは自動派ですか?キッチリ派ですか?

🙂:便利なデフォルト composable 集

普段使うであろう便利な機能(要は関数みたいなもの)が Nuxt 3 用にたくさん準備されていて、これを好きなところで使えます(実際にはサーバー/クライアント側のどちらで実行される場所なのか考える必要はある)。

仕組みとしては Vue 3 で使える composables に上述の自動インポートが組み合わさっただけのものではあります。が、自動でインポートされるということはやはりここでも使用に際する心理的なハードルが低いことを意味します!

  • useFetch
  • useRoute(r)
  • useState

あたりは最初からよく使うことになると思います。データフェッチの際に $fetch (ohmyfetch) もインポートせずに使えるのが特に好きです。

ちなみにいまは全部で 14 個あります。

😑:SSR/SSG 周辺の仕組み全体のややこしさ

「ややこしい」こと自体は課題かもしれませんが、その「理解が難しい」ことに関してはドキュメントが整備されていれば解決する問題かもしれません。…という意味で前述の "除外" を完全に遂行できていないのですが、それでもだいぶ困った部分ではありました。

全部つくりおわったあとに「あれこれ SSG の意味なんもなくね?」みたいになったりもしたのでベータ版状態のものに触れるというのはやはりとてもよい経験でした。もちろん僕の経験不足も含まれています。

なお、この記事執筆時点で ISR についてだけは未対応なことが公言されているのでちゃんと話から除外しているつもりです。

勝手に Q&A のコーナー

みなさんから頂戴したたくさんの質問(※)に答えていくコーナーです。
※ない

Zenn の参考にした部分は?

この質問でいうと「catnose さんの成果物は昔から参考にしまくっています」という回答のほうが正確で、「UI で困ったら氏の制作物かもしくは GitHub (誰かのリポジトリという意味ではなくそのもの自体)を見に行く」というのがクセになっています。

自分のブログ運営の中で色々な課題に気づき始めたころ、「記事を書いて Git で管理する」という体験をしたのがずっと頭から離れなかったのは言うまでもありません。

しかしです。ありふれていそうなアイデアではあるものの(事実つくり終わってからわかったのですが GitHub 連携できるヘッドレス CMS なんていっぱいありました…)、明らかに Zenn が意識されたようなサイトの雰囲気になってしまっては絶対にいけないなと考えていました。

その悩みの結果として、例えば記事ページではほとんどの人が気づかなそうな「修正リクエスト機能」がひっそり備わることとなります。

mirumi-tech-2
それ以外のページでは普通にプロフィールページへのリンクになっています。

そのほか「毎回アイキャッチを考えるコスト」に対して「絵文字」というソリューションを見いだしていることに本当に感動しますし(これは氏の 個人ブログ が発祥だったのかなと思っています)、いいなと思うアイデアは挙げればキリがないです。

リスペクトという意味での「モチーフ」や「オマージュ」に対しての「パクり」、これはあまりにも境界が難しくてどの業界でも解決しきれていない問題に感じます。良いものはぜひ参考にしたいわけですが、相手がどう思うかの気持ちは常に忘れたくないなと改めて思いました。
(今回つくったサイトは大丈夫と思ってこんな文章を書いています…)

急にオタクくさい内容になってしまい申し訳ありませんでした。でもずっと catnose さんのファンだったのは本当です! :)

なんで Nuxt のデプロイ先も AWS なの?

趣味です。

プチ自慢ポイントは?

モーダルって出現するときに背景のスクロールを封じることが多いじゃないですか。
でもこれをただ

body.modal {
    overflow-y: hidden;
}

のようにしてしまうと「スクロールバーのありなしが変化する分画面がちょっとズレる」のですよね。

あれが気になってしまうことがよくあったので、今回からマイモーダルコンポーネントをちょっとだけ進化させてみました。

現在のページの高さがビューポートを超えている場合は body の右側にスクロールバー分の padding を与えます:

body.modal {
    overflow-y: hidden;
    padding-right: 16px;
}

これでガタガタしなくなります!

ぜひみなさんもやってみてくださいませ。
(PC 以外のデバイスでは一律で除外しておくのをお忘れなく)

一番「やっちまった」やつは?

それだらけなんですが、強いて一言でいうとするならば「車輪の再発明をめちゃくちゃやりまくった」ということに尽きます。

僕は頭より先に手が動くタイプなのですが、実現したい機能が見つかると「思いついたそこまでの経路でまずつくってしまう」ということが頻繁に起こるので、全部キレイに仕上げてからようやくググってみて「な、なんだと…?」みたいになります。

例えば記事ページの目次自動追従ハイライト、これは初めて書いた機能なのでその場で思いついたロジックで動いているのですが、あとから IntersectionObserver の MDN のページ を読んだらまさにその実装アプローチがズバリ課題として扱われている文脈で説明がなされていました。

toc
手持ちの低スペック PC でも動作に問題がないことは一応確認しています(MDN でも「そのロジックが何個もあるとなると醜いです」みたいに書かれていました)。

これらだけ見ると最高にアホなだけで終了なのですが、実際のところこの性格でよかったなーといまは思っています。

だってこれってまさに「失敗は成功のもと」ではないでしょうか。次からはこの機能が簡単に導入できることを知っているし、一度パワープレイで実装したことがあるという経験が役に立たないはずがありません。
(もちろん自分でつくった状態ではユーザーの体験そのものに影響が出てしまう場合はただちに直します!)

今回もたくさんの「失敗」ができました!

残っている問題(いっぱい)

抱えている問題のうち特に気になっているものの紹介(と誰かが助けてくれないかなという淡い期待)です。

ダークモード時の画像の扱い

ダークモード遷移するようになっているのはいいのですが、これがまた白系の画像(つまりほぼ全て)がとても浮きます。

さらに言えば「背景が透明であるような見え方を期待して作図したもの」などは画像の外周部分がとてもイマイチな感じになってしまいます。

mirumi-tech-3
自分は背景色によく #fffcf9 を使うのですが、それを見越して自分で用意する図の背景色も同じ色に決め打ちしていることが多いです。とりあえず移管したけどちょっと見るにたえないですね…。

いわゆる記事型サイトでダークモードをつくったのは初めてだったのでこのような問題にも今回初めて気がつきました。

とはいえ画像自体の色はそのコンテンツ固有のものなので特にケアしなくてよい、という考え方でも全然いいかなとは思います。自分で描くものは背景色や外周さえちゃんと考えて出力すれば解決できそうです。

画像に対して CSS で filter をかけてなんとなく自動ダーク対応する、などもかっこよさそうですが、肝心の画像の情報が見づらくならない保証がないのでこれも難しそうですね。

ページ遷移時のスクロールの挙動

一番困っています。

よく言われるスクロール位置復元系の話ではなく「ページ遷移後にページトップに移動してくれない」という問題です。

Nuxt 2 には scrollToTop という設定があったのですが、いまの Nuxt 3 には類似のものはありません。実装予定かどうかもメンテナーの方からはレスポンスがないみたいです(参考:scrollToTop in v3 ?)。

仕方ないので、上記スレッドの内容を参考にしつつカスタムフックを利用した自作プラグインで暫定対応をしています:

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook("page:start", async () => {
    window.scrollTo({
      top: 0,
      left: 0,
    })
  })
})

でもこれ、実際にサイトにアクセスしていただければわかるのですが挙動としてはだいぶ不審です。

https://mirumi.tech

mirumi-tech-scroll-to-top
一瞬だけ現在のページでのトップに移動したのが見えてから次のページへ遷移する

これを見たみなさんもたぶん

  • フックのタイミングを変えればいいんじゃない?
  • スクロールを smooth にすればマシになるんじゃない?

など色々思われると思うのですが、ひと通り試した中で現状が最もエンドユーザーの期待する挙動に近くかつスピード感含めてストレスの少ない状態かなと思ってこうしています。そもそも対症療法なのでどんぐりの背比べ感もあります…。

またもや僕がなにか盛大な勘違いをしているのか、単にベータ版であるからなのかが全然わかっていません。でも普通の Web アプリケーションフレームワークなら「ページ遷移したら一番上にいく」なんて暗黙すぎるルールな気がするんだけどな…?

詳しい方どなたか教えてください😭

記事内でカスタムクラスを使用するフロー

マークダウンから HTML への変換には python-markdown2 というライブラリを使用しているのですが、名前からもわかるとおり 2 じゃない 1 があるのにも関わらず調子に乗って新しいものを選んだがために「任意クラスの付加」機能がないということにあとから気づくことになります。

「本家にはあるけど実装しない理由が何かあるんでしょうか?」という issue を出してみたら「プルリク待ってるぜ!」と言われたのでこれはこのあと時間ができたら挑戦してみようと思っています。これも結果オーライ!ですね。

ちなみに今はどうしているかというと、直で HTML を書いた場合でも特に問題なくマークダウンのパースは通過するので .md 内に手動で書いています。頻度は少ないし自分的には全然これでいいかなとは思っている…。

さらにいうとページごとのカスタム CSS みたいなことも普通に可能で、マークダウンファイル内に

<style>
p img {
    width: 480px;
}
</style>

と書くとちゃんとページ内全体に作用します。多用は避けますが。

美しいタイポ

非常に大きな問題です。

https://github.com/mirumirumi/mirumi-tech-frontend/issues/4

おわりに

クライアント側とサーバー側でそれぞれのテンプレートリポジトリもつくってあるので、よろしければぜひこちらもどうぞ!!

https://github.com/mirumirumi/template-nuxt3

https://github.com/mirumirumi/template-sam-python

お読みいただきありがとうございましたー!

GitHubで編集を提案

Discussion

ログインするとコメントできます