WordPressからmicroCMSへ移行してブログをフルリニューアルした話
こんにちは!@Ryo54388667です!☺️
普段は都内でエンジニアとして業務をしてます!
主にTypeScriptやNext.jsといった技術を触っています。
ブログのリニューアルのプロセスで直面した課題や解決策について紹介したいと思います。
📌 はじめに
数年前に作成してから放置していたWordPressのブログがあり、「いつかヘッドレスCMSにしたいなー」と思いつつも、ずるずると月日が経っていました。。😇そんな感じで腰が重い状態が続いていたのですが、今回、「移行しよっ!!」と思い立ったのは、次の本業のプロジェクトに活かせそうだなと感じたからです!次のプロジェクトではSEOが大きな命題となるものです。個人のプロジェクトで得られた知見を本業に活かせれば良いなと思い、そこでこのヘッドレスCMSの移行は絶好のタイミングでした!そんな感じの諸々の変化があり、今回、フルリニューアルに至りました。
↓リニューアルしたもの
本記事では、そのプロセスで直面した課題や学びを共有します。けっこう、細かいものが多いですが、どなたかの役に立つと嬉しいです!
タスク管理にはNotionのボードビューを利用して進めました。すべては説明し切れないので、かいつまんで紹介していきますー!
📌 アプリケーション
技術スタック
- Next.js(App Router)
- TailwindCSS
richEditorのparserにhtml-react-parserを利用
Sanitizeに対しては非対応なのですが、html-react-parserを選択しました。
今回、記事の入稿はmicroCMSのほうで考えており、sinitize済みのHTMLの文字列がレスポンスされるので、問題ないのかなと。(参考)
プレーンなaタグをnext/linkに差し替えるなどの柔軟な実装もできます!
export const customReplaceOptions: HTMLReactParserOptions = {
replace: (domNode) => {
const props = attributesToProps(domNode.attribs);
switch (domNode.name) {
case "a":
const href = domNode.attribs.href;
return <CustomLink {...props} href={href}>{domToReact(domNode.children as DOMNode[], customReplaceOptions)}</CustomLink>;
レスポンスデータの型定義について
少し古い記事かもしれませんが、ZOZOさんの技術ブログを参考にしました。
microCMSの繰り返しフィールドやカスタムフィールドも型補完が効き、快適に開発できます〜
ブログの詳細ページはビルド時にプリレンダリング
ブログの一覧ページはキャッシュせず、常時、最新のデータをフェッチするようにしており、ブログの詳細ページについてはビルド時にプリレンダリングしています。ECRを利用する際、注意したいのは、ビルド時に記事情報をフェッチするので、Dockerfileで環境変数をあらかじめ設定しておく必要があります。
// Dockerfile
# Set the environment variables
ARG MICROCMS_SERVICE_DOMAIN
ARG MICROCMS_API_KEY
RUN touch .env
RUN echo "MICROCMS_SERVICE_DOMAIN=${MICROCMS_SERVICE_DOMAIN}" >> .env
RUN echo "MICROCMS_API_KEY=${MICROCMS_API_KEY}" >> .env
// .github/workflows/deploy.yml
with:
file: ./Dockerfile
context: .
push: true
build-args: |
"MICROCMS_SERVICE_DOMAIN=${{ secrets.MICROCMS_SERVICE_DOMAIN }}"
"MICROCMS_API_KEY=${{ secrets.MICROCMS_API_KEY }}"
provenance: false
ZennのRSS feed からデータを取得し、一覧ページを作成
ブログではなく、Zennで書いてきた記事一覧もあると良いかなと思ったので、ZennのRSSで取得したデータを元に一覧ページを作成しました。下記の記事を参考に、jsonファイルを作成しています。大感謝です🙏
シンタックスハイライトのライブラリのバンドルサイズが大きくなってしまう問題
バンドルサイズを計測すると、シンタックスハイライトで利用しているreact-syntax-highlighterのモジュールのサイズが巨大でした。。😇
// before
import {OneDark} from 'react-syntax-highlighter/dist/esm/styles/prism';
↓
//after
import oneDark from 'react-syntax-highlighter/dist/esm/styles/prism/one-dark';
必要なモジュールのみimportするように修正しました。今後、react-syntax-highlighterを利用する人の参考になれば幸いです!
アクセシビリティの指標で「続きを読む」リンクで警告が出る
lighthouseでページを計測すると、アクセシビリティの指標で 「Links do not have descriptive text」 と警告が出ます。詳細を見ると、 「続きを読む」というリンクのテキストが不適切である というものです。とはいえ、仕事柄、色々な人のブログを拝見しているのですが、「続きを見る」というリンクを頻繁に見かけますし、問題があるように思えませんでした。そんな折、同じ課題にぶつかった人がいらっしゃいました!その方のブログを参考にしました!ありがとうございます🙏
リンクのtitleの箇所にsr-only
を付与しました。domは存在するが、視覚的には非表示で、screen readerで有効なテキストになります。
Use sr-only to hide an element visually without hiding it from screen readers:
https://tailwindcss.com/docs/screen-readers
URLの文字の折り返しが効かない
他の文字列は折り返しが効くのに、URLの文字列だけ折り返しが効かず、DOMのエリアを超えてしまい、モバイル版のUIが崩れてしまう問題がありました。
break-all
で半ば強制的に折り返すようにしています。
microCMSの画像ドメインをリソースヒントで指定する
microCMSを利用すると、画像のドメインは、運用しているアプリケーションのドメインとは異なるので、画像の表示が遅くなることがあるかと思います。その事象の対策として、microCMSの画像ドメインをリソースヒントで指定し、なるべく迅速な通信をするようにしました。
// layout.tsx
<html lang="ja">
<PreloadResources />
<body>
{children}
</body>
</html>
'use client'
import ReactDOM from 'react-dom'
const IMAGE_DOMAIN = 'https://images.microcms-assets.io'
const PROJECT_ID = ‘xxxxxxxx’
const PreloadResources = () => {
ReactDOM.preconnect(`${IMAGE_DOMAIN}/assets/${PROJECT_ID}`, { crossOrigin: "anonymous" })
ReactDOM.prefetchDNS(IMAGE_DOMAIN)
return null
}
export default PreloadResources
↓App Routerにおけるresource hintsの書き方
Blurの設定について
上のリソースヒントと同様で、画像の表示までのバッファに対して何かできることはないかと思ってこの施作を行いました。ただ、結論から言うと、局所的な箇所にのみ利用する形にしました。というのも、全ての箇所に入れたらパフォーマンスが著しく落ちてしまったり、通信量が多くなってしまったからです。自分の実装方法が悪かったのも否定できないのですが、一部分のみBlurの設定をするようにしました。
↓ パフォが悪くなった期間
↓ なぜか通信量が跳ね上がる。。😇
埋め込みカードの実装について
地味に時間がかかりました。。😇
- 同一プロジェクト内に埋め込み用のページを作成する。(例: app/embedded/page.tsx)
- iframeのsrc属性に作成したページのパスを指定する。(<iframe src=“/embedded/xxxxx” />)
詳細は下記にまとめました!
p.s.
最後に余談ですが、地味に「New」ラベルの色味は気に入っています☺️
const NewLabel = () => {
return (
<span className="rounded-full bg-gradient-to-r from-[#FF6B6B] to-[#FFA500] px-3 py-1 text-xs tracking-widest text-white">
New
</span >
);
}
export default NewLabel;
📌 インフラ
技術スタック
- Terraform
- AWS App Runner
- Amazon ECR
- Amazon Route 53
- AWS WAF
- ACM (AWS Certificate Manager)
- CloudWatch RUM
前段にCloudFrontのようなCDNを挟もうとしたけど。。
前段にCloudFrontを挟もうとしたのですが、lighthouseの点数が下がったので今回は見送りました。もしかすると、CloudFrontはTransfer-Encoding: chunked
に対応していないのかなと予想しています。もう少し調査したいところです。
パフォーマンス監視について
パフォーマンスに関して、AWS CloudWatch RUM を利用しています。
主要なWeb Vitals を取得したり、セッション情報を取得したりできます。パフォーマンスの数値が著しく落ちた時にアラートを出すようなこともできると良いのかなと思っています。
運用コストについて
自分のメディアでは、そこまで同時接続は無いので、今月は最もスペックの低いインスタンスで運用していました。アラートを見る限り、サーバーは落ちていないように思います。その状態だとコスト感は以下のようになります。
Cost/Day
- App Runner : 0.1$
- WAF : 0.2$
Cost/Month
- Route53 : 1.0$
- TAX : 数$
これまでレンタルサーバーで運用していたものとトントンくらいの料金になりました。弱小メディアの例で恐縮ですが。。WAFのRuleを追加したり、VPCを経由するなどセキュリティの施作を厚くすると当然コストは上がります。
AWS AmplifyでホスティングしたStorybookをサブドメインで運用
Route53でサブドメインを設定し、CNAMEレコードにAWS AmplifyでホスティングしたStorybookを指定しています。story.ryotablog.jp
StorybookのAmplifyへのホスティング方法については下記の記事にまとめています。詳細はご覧ください。
📌 最後に
アプリケーション
- richEditorのparserにhtml-react-parserを利用
- レスポンスデータの型定義について
- ブログの詳細ページはビルド時にプリレンダリング
- ZennのRSS feed からデータを取得し、一覧ページを作成
- アクセシビリティの指標で「続きを読む」リンクで警告が出る
- URLの文字の折り返しが効かない
- microCMSの画像ドメインをリソースヒントで指定する
- Blurの設定について
- 埋め込みカードの実装について
インフラ
- 前段にCloudFrontのようなCDNを挟もうとしたけど。。
- 運用コストについて
- AWS AmplifyでホスティングしたStorybookをサブドメインで運用
今後はzennに書くほどでもないものとか、雑記などを自分のメディアに書いていきたいと思います。
より良い方法があれば教えてください〜
最後まで読んでいただきありがとうございます!
気ままにつぶやいているので、気軽にフォローをお願いします!🥺
Discussion