Closed15

Next.js+Vercelで自分のブログを作ってみる

taka_htaka_h

今まではnoteをブログとして使っていたけど、いい加減自分専用の発信場としてのサイトが欲しくなり、Next.js+Vercel+headlessCMSのJamstackアーキテクチャで作ってみることに。

taka_htaka_h

実は半年ほど前に、Next.js+Netlifyでポートフォリオサイトっぽいものを作ってはいた。

https://laughing-pasteur-060eb0.netlify.app/

最初はこのサイトにブログ機能を付け足そうかとも思ったけど、今見るとデザインがなんか気に入らないので、一から全部作り直すことにした。

taka_htaka_h

以前サイトを作った際、ホスティングサービスは割となにも考えずにNetlifyを使ったけど、今回はVercelを選択。

https://vercel.com/home

Vercelを選択した理由は、以下の通り。

  1. プロジェクトのビルド時間、サイトの表示速度などは、NetlifyよりVercelの方が良いらしい。
  2. Next.jsを開発している企業のサービスなので、Next.jsとの相性が良さそう。


なので、まずはVercelの公式ドキュメントを色々と読み漁った。その後、サーバーレス関数の機能を使って簡単なLINE Botなどを作ってみたりして、Next.js+Vercelでできることを色々と試した。ついでにNext.jsやSWRのドキュメントも自分で訳して色々と読んでみた。

ホスティングサービスとしてのVercelもNext.jsもSWRも、やはり同じ組織が開発しているだけあり、技術としての目指すところが一貫しているので、体系的に学びやすいと感じた。

taka_htaka_h

デザインツール

サイトのデザインについても、一度ちゃんとしたツールを使ってやってみたいと思っていたので、Adobe系のソフト、Figma、Framerなどを色々調べた。

ざっと調べて検討した結果、最終的にFigmaを選択。

https://www.figma.com/

Figmaの良いところ

  1. 無料で導入できる。
  2. マルチデバイス対応で、Webブラウザ、Mac、Windowsから使える。
  3. デザインのバージョン管理ができる。(※無料版は過去30日まで)
  4. デザインの各要素をコンポーネント化できる。



デザインのバージョン管理ができるところ、Reactと同じくコンポーネントという概念があるところが、特に気に入っている。使い始めて約1ヶ月経つけど、今のところとても使いやすい。

taka_htaka_h

スタイリング

前回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でも変数や演算が使えるらしいので、手軽なフレームワークに頼るよりも、まず一度その辺を追求してみたかった、という思いもあった。

taka_htaka_h

ブログデータの管理

Next.jsでブログを作る場合、ブログデータを(公式チュートリアルで作るブログのように)ローカルのファイルシステムで管理する方法と、headlessCMSで管理する方法があるが、今回はmicroCMSをheadlessCMSとして使う方法を採用。

microCMSは国内サービスで、ドキュメントもわかりやすく、公式ブログではJamstackに関する記事が色々と公開されていて、大変参考になる。

「NetlifyCMSの方がMarkdown形式の入力に強い」と聞き、一時そっちに移行しようかと検討しかけたけど、microCMSでも昨年9月からMarkdown記法が使えるようになってるし、Markdown形式でコンテンツを取得する機能を検討しているそうなので、それを信じて今後も使い続けるつもり。

https://blog.microcms.io/markdown-is-available/

taka_htaka_h

MarkdownとMDX

先日、MDXの存在を知り、早速現在作っている自分のブログサイトに取り入れてみることにした。

MDXとは

簡単に言えば、JSXをそのまま記述できるMarkdown。

公式の例にもある通り、.mdxのファイルにMarkdown形式で記述し、さらに同じファイル内でReactコンポーネントをインポートして、それをそのまま使うことができる。すごい😯

example.mdx
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を選択した」とブログの記事でも語られている。

https://blog.tailwindcss.com/building-the-tailwind-blog

公開されているソースコードを見てもらうとわかるように、pages/ディレクトリ内に配置された各.mdxファイルの内容が、そのまま各ブログ記事のページとして表示されるようになっている。すごい!😵

外部からMDXデータを引っ張ってくる場合

Tailwind CSSのブログのように、.mdxファイルをローカルで管理する場合はシンプルだけど、データをmicroCMSのような外部ソースから引っ張ってくる場合は、なかなか厄介。

自分の場合は、最終的に以下のライブラリを導入して解決した。

https://github.com/hashicorp/next-mdx-remote

このライブラリでは、サーバー側で使う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/linknext/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>に差し替える、といったこともできるらしいし。

https://dev.to/jameswallis/how-to-use-the-remark-markdown-converters-with-next-js-projects-a8a


というわけで、現在絶賛作り直し中。

taka_htaka_h

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.

シリアライズできないデータをgetStaticPropspropsに含めてreturnさせようとすると、エラーになるらしい…(まあ、そりゃそうか)

ただその辺は上手く調整できたし、とりあえず、やりたいことは概ね達成できそう。

taka_htaka_h

MDXでやろうとしていたこと、rehype-reactで大体できると考えていたけど、意外と曲者だった。

TypeScriptと使ってると、オプションに置き換えたいカスタムコンポーネントを渡した時に「型が違う」とエラーがでる…ReactNodeが渡せないなら一体何渡せばいいのよ…(´ω`)

tsconfig.jsonstrictFunctionTypesfalseにすれば、一応エラーは消えてビルドできるけど、なんだかなー

taka_htaka_h

コードブロックのハイライトについて

MDXを使おうとしていた時は、prism-react-rendererとかreact-syntax-highlighterとかのライブラリでカスタムの<CodeBlock>コンポーネントを作って、それをMDX内で使おうと考えてたんだけど、rehypeのプラグインで処理した方が余程スマートだった…unifiedほんと便利(´ω`)

さらにrehype-reactを使えば、MDXでやりたかったことも色々達成できる。せっかくなので、この辺りは別途記事にまとめようかと思う。

taka_htaka_h

Vercelに本番環境へデプロイしたものの、各ブログ記事にアクセスすると500: internal server errorが表示される…microCMSからプレビューモードを使おうとしても同様。

Vercelへの本番環境へのデプロイ自体は成功しているし、ローカルでのビルド&スタートも普通に成功する…🤔

taka_htaka_h

VercelのFunction Logsを見ると、no such file or directory, open '/var/task/node_modules/***エラーが出ている…
どうもブログ投稿日時表示に使っているdate-fnsと、コードブロックのハイライトに使っているshikiが見つからないらしい…

本番に上がっていないパッケージがあると言うことか…???

taka_htaka_h

Vercelからドメインを購入しようとしたんだけど、何度試しても決済時にエラーが起きて失敗する…😢
諦めてAWS Route53から購入する。Vercelの価格より$10ほど安かった。

taka_htaka_h

ブログを公開!しました!

https://takahira.io/blog

他サービスへのシェア機能とか、まだ実装できてないところや、色々と気になるところもあるけど、今後徐々にブラッシュアップしていく予定です。

開発にあたり、思った上につまずきポイントが多かったけど、逆に言えば記事にするネタが増えたということなので、随時対処して、他の人にも共有したいなーと思ったことはZennの方に投稿していこうかと思います。

このスクラップは2021/04/04にクローズされました