Next.js+Vercelで自分のブログを作ってみる
今まではnoteをブログとして使っていたけど、いい加減自分専用の発信場としてのサイトが欲しくなり、Next.js+Vercel+headlessCMSのJamstackアーキテクチャで作ってみることに。
実は半年ほど前に、Next.js+Netlifyでポートフォリオサイトっぽいものを作ってはいた。
最初はこのサイトにブログ機能を付け足そうかとも思ったけど、今見るとデザインがなんか気に入らないので、一から全部作り直すことにした。
以前サイトを作った際、ホスティングサービスは割となにも考えずにNetlifyを使ったけど、今回はVercelを選択。
Vercelを選択した理由は、以下の通り。
- プロジェクトのビルド時間、サイトの表示速度などは、NetlifyよりVercelの方が良いらしい。
- Next.jsを開発している企業のサービスなので、Next.jsとの相性が良さそう。
なので、まずはVercelの公式ドキュメントを色々と読み漁った。その後、サーバーレス関数の機能を使って簡単なLINE Botなどを作ってみたりして、Next.js+Vercelでできることを色々と試した。ついでにNext.jsやSWRのドキュメントも自分で訳して色々と読んでみた。
ホスティングサービスとしてのVercelもNext.jsもSWRも、やはり同じ組織が開発しているだけあり、技術としての目指すところが一貫しているので、体系的に学びやすいと感じた。
デザインツール
サイトのデザインについても、一度ちゃんとしたツールを使ってやってみたいと思っていたので、Adobe系のソフト、Figma、Framerなどを色々調べた。
ざっと調べて検討した結果、最終的にFigmaを選択。
Figmaの良いところ
- 無料で導入できる。
- マルチデバイス対応で、Webブラウザ、Mac、Windowsから使える。
- デザインのバージョン管理ができる。(※無料版は過去30日まで)
- デザインの各要素をコンポーネント化できる。
デザインのバージョン管理ができるところ、Reactと同じくコンポーネントという概念があるところが、特に気に入っている。使い始めて約1ヶ月経つけど、今のところとても使いやすい。
スタイリング
前回Next.jsでサイトを作った当時は、styled-components
+Tailwind CSS
の構成が流行ってたっぽかったので、それでスタイリングしていたんだけど、個人的にJavaScriptにCSSを書いていくCSS-In-JS
は、なんかあんまり使いやすいとは思えず、さらに最近のNext.jsはCSS Modulesを推しているっぽいので、今回はCSS Modulesを使うことにした。
同じくNext.jsが推しているらしいTailwind CSS
も使うか検討したけど、今回はSCSSを自分で書いていくことにした。予め用意されているユーティリティクラスを組み合わせてスタイリングしていくというTailwind CSS
の考え自体は賢いとは思いつつ、やっぱ要素のclassにクラス名がズラララっと並ぶのがどうしても好きになれず…
SCSSでも変数や演算が使えるらしいので、手軽なフレームワークに頼るよりも、まず一度その辺を追求してみたかった、という思いもあった。
ブログデータの管理
Next.jsでブログを作る場合、ブログデータを(公式チュートリアルで作るブログのように)ローカルのファイルシステムで管理する方法と、headlessCMSで管理する方法があるが、今回はmicroCMSをheadlessCMSとして使う方法を採用。
microCMSは国内サービスで、ドキュメントもわかりやすく、公式ブログではJamstackに関する記事が色々と公開されていて、大変参考になる。
「NetlifyCMSの方がMarkdown形式の入力に強い」と聞き、一時そっちに移行しようかと検討しかけたけど、microCMSでも昨年9月からMarkdown記法が使えるようになってるし、Markdown形式でコンテンツを取得する機能を検討しているそうなので、それを信じて今後も使い続けるつもり。
MarkdownとMDX
先日、MDX
の存在を知り、早速現在作っている自分のブログサイトに取り入れてみることにした。
MDX
とは
簡単に言えば、JSX
をそのまま記述できるMarkdown。
公式の例にもある通り、.mdx
のファイルにMarkdown形式で記述し、さらに同じファイル内でReactコンポーネントをインポートして、それをそのまま使うことができる。すごい😯
import { Chart } from '../components/chart'
# Here’s a chart
The chart is rendered inside our MDX document.
<Chart />
有名なところだと、Tailwind CSSの公式ブログがNext.js
+MDX
で作られている。「MDX
を使いたかったので、MDX
のサポートが手厚いNext.js
を選択した」とブログの記事でも語られている。
公開されているソースコードを見てもらうとわかるように、pages/
ディレクトリ内に配置された各.mdx
ファイルの内容が、そのまま各ブログ記事のページとして表示されるようになっている。すごい!😵
外部からMDXデータを引っ張ってくる場合
Tailwind CSSのブログのように、.mdx
ファイルをローカルで管理する場合はシンプルだけど、データをmicroCMSのような外部ソースから引っ張ってくる場合は、なかなか厄介。
自分の場合は、最終的に以下のライブラリを導入して解決した。
このライブラリでは、サーバー側で使うrenderToString()
関数と、クライアント側で使うhydrate()
関数が提供されている。
まず、Next.jsのgetStaticProps
内(つまりサーバー側)で、外部ソースから取得したMDXデータをrenderToString()
で解析。その結果をpropsを通してページコンポーネントに渡し、それを受け取った hydrate()
関数がクライアント側でコンポーネントをレンダリングする。(以下は公式が提示している例)
import renderToString from 'next-mdx-remote/render-to-string'
import hydrate from 'next-mdx-remote/hydrate'
import Test from '../components/test'
const components = { Test }
export default function TestPage({ source }) {
const content = hydrate(source, { components })
return <div className="wrapper">{content}</div>
}
export async function getStaticProps() {
// MDX text - can be from a local file, database, anywhere
const source = 'Some **mdx** text, with a component <Test />'
const mdxSource = await renderToString(source, { components })
return { props: { source: mdxSource } }
}
これでmicroCMS側でブログ記事を入稿する際、MDX形式の記述を使えるようになる。カスタムコンポーネントはもちろん、Next.jsが提供するnext/link
やnext/image
も、記事の中でそのまま記述して使える。やったね!(۶`∀´)۶
…だがしかし、やっぱり普通のMarkdownに戻すことにした
理由は以下の通り。
外部ソースでMDXを扱うのは、やっぱり色々と面倒
next-mdx-remote
を使用している場合、MDX内でのReactコンポーネントのインポートが使えない。MDXでカスタムコンポーネントを使用したい場合は、前述のrenderToString()
やhydrate()
に、予め使いたいコンポーネントを渡しておく必要がある。
さらに、ローカル環境の.mdx
ファイルにVS Codeで記述している場合は、「〇〇をインポートしようとしているけど、見つからないよ!」、「このコンポーネントには××プロパティーを渡さないとだめだよ!」と、色々と教えてくれたりするが、当然headlessCMSのエディタはそんなことまではしてくれない。
正直、普段VS Codeの快適さに飼い慣らされている身としては、リンターもコードフォーマッタもない環境でJSXが扱える気がしなかった…
自分のブログに、MDXはオーバースペックな気がした。
ブログで技術系の記事を書くなら、正直Markdownで全然事足りる。逆に、記事の中でReactのカスタムコンポーネントを直接書き込みたい場面というのが、想像できなかった。
記事内でnext/link
とかnext/image
が使いたい場面もあるだろうけど、別にわざわざ記事の中に直接書き込めるようにしなくても、unified
のパッケージとか使えば、条件に応じて<a>
タグを<Link>
に差し替える、といったこともできるらしいし。
というわけで、現在絶賛作り直し中。
MDXは使わないことに決めたので、やりたいことを達成するために、unifiedでMarkdownを処理することにした。
unifiedのパッケージ群で処理したブログ記事のデータの最終的な結果を、getStaticProps
からreturnさせようとしたら、Next.jsから「JSON化できないデータをgetStaticProps
に渡すな!😡」と怒られた…
SerializableError: Error serializing `.postData.reactElment.$$typeof` returned from `getStaticProps` in "/posts/[id]".
Reason: `symbol` cannot be serialized as JSON. Please only return JSON serializable data types.
シリアライズできないデータをgetStaticProps
のprops
に含めてreturnさせようとすると、エラーになるらしい…(まあ、そりゃそうか)
ただその辺は上手く調整できたし、とりあえず、やりたいことは概ね達成できそう。
MDXでやろうとしていたこと、rehype-react
で大体できると考えていたけど、意外と曲者だった。
TypeScriptと使ってると、オプションに置き換えたいカスタムコンポーネントを渡した時に「型が違う」とエラーがでる…ReactNode
が渡せないなら一体何渡せばいいのよ…(´ω`)
tsconfig.json
のstrictFunctionTypes
をfalse
にすれば、一応エラーは消えてビルドできるけど、なんだかなー
コードブロックのハイライトについて
MDXを使おうとしていた時は、prism-react-renderer
とかreact-syntax-highlighter
とかのライブラリでカスタムの<CodeBlock>
コンポーネントを作って、それをMDX内で使おうと考えてたんだけど、rehype
のプラグインで処理した方が余程スマートだった…unifiedほんと便利(´ω`)
さらにrehype-react
を使えば、MDXでやりたかったことも色々達成できる。せっかくなので、この辺りは別途記事にまとめようかと思う。
Vercelに本番環境へデプロイしたものの、各ブログ記事にアクセスすると500: internal server error
が表示される…microCMSからプレビューモードを使おうとしても同様。
Vercelへの本番環境へのデプロイ自体は成功しているし、ローカルでのビルド&スタートも普通に成功する…🤔
Vercelからドメインを購入しようとしたんだけど、何度試しても決済時にエラーが起きて失敗する…😢
諦めてAWS Route53から購入する。Vercelの価格より$10ほど安かった。
ブログを公開!しました!
他サービスへのシェア機能とか、まだ実装できてないところや、色々と気になるところもあるけど、今後徐々にブラッシュアップしていく予定です。
開発にあたり、思った上につまずきポイントが多かったけど、逆に言えば記事にするネタが増えたということなので、随時対処して、他の人にも共有したいなーと思ったことはZennの方に投稿していこうかと思います。