はじめに
こんにちは。2021年に新卒でニフティに入社し、今年度で2年目になりました。そろそろポートフォリオサイトを作って、自分がやってきたことをアウトプットしたいと思いました。
そこで今回は、Next.jsとmicroCMSを使ってポートフォリオサイトを作っていきます!
microCMSとは、日本製のヘッドレスCMSです。日本製なのでドキュメントが全て日本語に対応されており、丁寧にまとめられています。なので利用しやすいことで人気を集めています。今回はこちらを使っていきたいと思います。簡略化のため、Next.jsのチュートリアルをもとにmicroCMSを導入していきたいと思います。
Next.jsのチュートリアルをたどれば、ブログサイトは作成できるのですが、ローカルにファイルを保存しているのであまりイケていません。microCMSを使って、コンテンツをクラウド上で管理し、APIで呼び出せるようにしていきたいと思います。
今回作成したソースコードはGitHub上に公開していますので、試したい方はそちらからcloneしていただき、次のステップで実施するmicroCMSの準備をしていただくと、すぐに動かすことができます!
ソースコードはこちら
また、Vercelにデプロイしているので実際の動作を確認いただけます!
https://nextjs-tutorial-git-feature-microcms-k0825.vercel.app/
microCMSの準備
microCMSを準備していきます。まず、microCMSのページにアクセスし、新規登録でアカウントを作成します。アカウント登録画面から登録します
アカウントを登録したら、ログイン画面からログインすることができます。メールアドレス、パスワードを入力しログインしてください。
ログインすると、サービス情報を入力という画面に遷移します。
好きなサービス名とサービスIDを入力してください。
今回は以下のように設定しました。
- サービス名: techblog
- サービスID: ikarigawa-techblog
「サービスを作成」をクリックし、サービスを作成します。
以下のような画面が出れば登録完了です!
表示されているURLにアクセスすると管理画面に移動できます。
管理画面からAPIを作っていきます。テンプレートから「ブログ」を選択し、APIを作成します。
しばらく待つと、以下のような画面に遷移します。
左メニューのコンテンツ(API)を確認すると、「ブログ」、「カテゴリ」というAPIが作成されていることがわかります。また、それぞれクリックすると、各コンテンツ一覧を見ることができます。
これでmicroCMS側の設定は完了しました。他にも色々設定したり、確認したい場合は、microCMSのドキュメントをご参照ください!
Next.jsの準備
次にNext.jsを準備していきます。
Next.jsプロジェクトの作成
今回はNext.jsのチュートリアルで作成した、完成系のものを使っていきます。以下のコマンドをターミナルやコマンドプロンプトで入力し、Next.jsプロジェクトを作成します。
1 |
npx create-next-app nextjs-blog --use-npm --example "https://github.com/vercel/next-learn/tree/master/basics/basics-final" |
作成が完了したら、開発サーバーを立ち上げてみます。
1 2 |
cd nextjs-blog npm run dev |
localhost:3000にアクセスすると下記の画像のようにアプリケーションが立ち上がります。
このままでも良いですが、Your Name
とYour Self Introduction
を自分のものに更新してみてください!変更箇所は以下になります。
- public/images/profile.jpgを任意の写真に置き換える
components/layout.js
のconst name = '[Your Name]'
pages/index.js
の<p>[Your Self Introduction]</p>
以下のように修正しました
postsディレクトリにマークダウンファイルがあると思います。こちらのディレクトリにファイルを保存することで、このページに表示される仕組みとなっています。
ちなみに、ファイルの中身は以下のようになっています。
1 2 3 4 5 6 7 8 9 10 11 |
--- title: "Two Forms of Pre-rendering" date: "2020-01-01" --- Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page. - **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request. - **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**. Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others. |
ファイルの上のほうで記事のタイトルと日付を設定し、その下に本文があるという形になっているようです。
今回はこちらにファイルを置いて管理するのではなく、microCMSに保存してAPIでデータを取り出すように修正していきます。
環境変数の設定
microCMSのAPIへアクセスするためには、リクエスト先のサービスドメインとAPIキーを指定する必要があります。
.env.development.localファイルを作成します。.localをつけるとローカル環境で使うことができ、.developmentをつけると開発環境で使うことができます。
1 |
touch .env.development.local |
作成し、APIキーとサービスドメインの情報を記述します。
- サービスドメインは、
ikarigawa-techblog.microcms.io
のikarigawa-techblog
の部分です。 - APIキーは以下の画像の赤枠部分をクリックすることで確認することができます。
1 2 |
API_KEY=XXXXXXXXXXXXXX SERVICE_DOMAIN=XXXXXXXXXXXXXXX |
microcms-js-sdkをインストール
次に、公式で提供しているmicroCMSのパッケージである、microcms-js-sdk
をインストールします。
1 |
npm install microcms-js-sdk |
lib/clients.js
を作成し、SDKの初期化を行います。
1 |
touch ./lib/clients.js |
serviceDomain
とapiKey
の値は.env.development.local
を参照します。
1 2 3 4 5 6 |
import { createClient } from "microcms-js-sdk"; export const client = createClient({ serviceDomain: process.env.SERVICE_DOMAIN, apiKey: process.env.API_KEY, }); |
これで準備が整いました!
記事一覧を表示
実際にmicroCMSから記事を取得するようにソースコードを変更していきます。
lib/posts.jsにてデータをフェッチし、各コンポーネントにデータを渡しているので、こちらを開いていきます。
lib/posts.js
lib/posts.jsのgetSortedPostsDataにてIDとそのファイルの中身を取得し、ソートして返却しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
export function getSortedPostsData() { // Get file names under /posts const fileNames = fs.readdirSync(postsDirectory); const allPostsData = fileNames.map((fileName) => { // Remove ".md" from file name to get id const id = fileName.replace(/\.md$/, ''); // Read markdown file as string const fullPath = path.join(postsDirectory, fileName); const fileContents = fs.readFileSync(fullPath, 'utf8'); // Use gray-matter to parse the post metadata section const matterResult = matter(fileContents); // Combine the data with the id return { id, ...matterResult.data, }; }); // Sort posts by date return allPostsData.sort((a, b) => { if (a.date < b.date) { return 1; } else { return -1; } }); } |
コードを修正していきます。まず、事前準備にて作成した、client.jsをimportします。
そして、allPostsDataという変数にmicroCMSから取得したデータを代入します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import { client } from './client'; export async function getSortedPostsData() { const data = await client.get({ endpoint: "blogs" }); const allPostsData = data.contents; return allPostsData.sort((a, b) => { if (a.createdAt < b.createdAt) { return 1; } else { return -1; } }); } |
pages/index.js
1 2 3 4 5 6 7 8 |
export async function getStaticProps() { const allPostsData = getSortedPostsData() return { props: { allPostsData, }, }; }; |
getSortedPostsDataを呼び出す箇所にawaitをつけるだけです。
1 2 3 4 5 6 7 8 |
export async function getStaticProps() { const allPostsData = await getSortedPostsData(); return { props: { allPostsData, }, }; }; |
ページにアクセスすると以下のようなエラーが発生します。
どうやら日付表示周りでエラーが出ているようです。ここで、APIがどんなリクエストを返すか見ていきたいとおもいます。
1 2 3 4 5 6 7 8 9 |
export default function Home({ allPostsData }) { console.log(allPostsData); return <></>; // return ( // : // ここのかっこが閉じるまでコメントアウト // : // ); } |
ページを開き、開発者ツールを開きます。すると、コンソールが表示されAPIからのリクエスト結果が表示されます。
APIのリクエストは、id
, title
, content
, createdAt
などが返されます。APIの返却値はmicroCMSのドキュメントをご参照ください。
ソースコードに戻り、処理を確認してみます。
APIからはdate
が返されていませんが、allPostsData
のmap
でdate
が使われています。dateがないため、エラーになってしまっているようです。 なので、こちらを修正する必要がありそうです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
export default function Home({ allPostsData }) { return ( <Layout home> <Head> <title>{siteTitle}</title> </Head> <section className={utilStyles.headingMd}> <p>Pomeranians are typically friendly, lively and playful.</p> <p> (This is a sample website - you’ll be building a site like this in{" "} <a href="https://nextjs.org/learn">our Next.js tutorial</a>.) </p> </section> <section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}> <h2 className={utilStyles.headingLg}>Blog</h2> <ul className={utilStyles.list}> {allPostsData.map(({ id, date, title }) => ( <li className={utilStyles.listItem} key={id}> <Link href={`/posts/${id}`}> <a>{title}</a> </Link> <br /> <small className={utilStyles.lightText}> <Date dateString={date} /> </small> </li> ))} </ul> </section> </Layout> ); } |
以下のように、dateをcreatedAtに修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
export default function Home({ allPostsData }) { return ( <Layout home> <Head> <title>{siteTitle}</title> </Head> <section className={utilStyles.headingMd}> <p>Pomeranians are typically friendly, lively and playful.</p> <p> (This is a sample website - you’ll be building a site like this in{" "} <a href="https://nextjs.org/learn">our Next.js tutorial</a>.) </p> </section> <section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}> <h2 className={utilStyles.headingLg}>Blog</h2> <ul className={utilStyles.list}> {allPostsData.map(({ id, createdAt, title }) => ( <li className={utilStyles.listItem} key={id}> <Link href={`/posts/${id}`}> <a>{title}</a> </Link> <br /> <small className={utilStyles.lightText}> <Date dateString={createdAt} /> </small> </li> ))} </ul> </section> </Layout> ); } |
再度ページを見てみます。すると、今まで表示されていたファイルに保存しているデータが表示されずに、 新しくmicroCMSに保存されているデータのタイトルが表示されています。
こちらで、記事一覧の取得は完了です!
microCMSに記事を追加し、ページにも同じようにタイトルが表示されることを確認してみてください!
次のセクションで記事詳細ページを表示できるようにエラーを解消し、画面遷移できるようにしていきます。
記事詳細画面にルーティング
記事のタイトルをクリックすると、記事の詳細ページへ遷移するように修正していきます。
lib/posts.js
lib/posts.jsのgetAllPostIdsにて、ファイル名をIDとして取得しています。こちらは動的ルーティングの処理で使用されており、例えばlocalhost:3000/posts/{ファイル名}のようにURLを指定すると、そのファイル名の記事詳細画面に遷移する仕組みとなっています。
1 2 3 4 5 6 7 8 9 10 |
export function getAllPostIds() { const fileNames = fs.readdirSync(postsDirectory); return fileNames.map((fileName) => { return { params: { id: fileName.replace(/\.md$/, ''), }, }; }); } |
1 2 3 4 5 6 7 8 9 10 |
export async function getAllPostIds() { const data = await client.get({ endpoint: 'blogs' }); return data.contents.map((content) => { return { params: { id: content.id, }, }; }); } |
pages/post/[id].js
呼び出し元は以下のようになっています。pages/posts/[id].js
のgetStaticPaths
です。こちらが動的ルーティングを実現するためのメソッドとなります。
1 2 3 4 5 6 7 |
export async function getStaticPaths() { const paths = getAllPostIds() return { paths, fallback: false } } |
以下のように修正します。awaitをつけるだけです。
1 2 3 4 5 6 7 |
export async function getStaticPaths() { const paths = await getAllPostIds() return { paths, fallback: false } } |
では、ページを見ていきます。
ページが見つからないとエラーが出てしまいました。まだmicroCMS用に詳細ページを作成していないので、この表示になってしまいます。
記事詳細画面を表示
lib/posts.js
記事が表示できるように修正していきます。記事ひとつひとつのデータはgetPostData
で取得しているようです。
IDからパスを作成し、ファイルの中身を取得しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
export async function getPostData(id) { const fullPath = path.join(postsDirectory, `${id}.md`); const fileContents = fs.readFileSync(fullPath, 'utf8'); // Use gray-matter to parse the post metadata section const matterResult = matter(fileContents); // Use remark to convert markdown into HTML string const processedContent = await remark().use(html).process(matterResult.content); const contentHtml = processedContent.toString(); // Combine the data with the id and contentHtml return { id, contentHtml, ...matterResult.data, }; } |
マークダウンからデータ整形をする必要があるので少々長くなっていましたが、APIからデータを受け取る形にすることでこうした処理が不要になります。
他のメソッドと同様に、APIからデータを受け取る形に修正していきます。記事を一つだけ受け取る場合はリクエストを投げる際に、endpoint
の他にcontentId
を指定してあげる必要があります。
以下のように修正します。APIから受け取ったデータをそのまま返すようになりました。
1 2 3 4 5 6 7 |
export async function getPostData(id) { const data = await client.get({ endpoint: 'blogs', contentId: id, }); return data; } |
ページを確認していきます。
トップページから任意のページにアクセスし、サンプルページを作成することができました!
おめでとうございます。これで自分のポートフォリオページを作ることができました!
さいごに
今回は、Next.jsのチュートリアルページを改良して、ローカルにデータを保持していた投稿をmicroCMSから取得できるようにしました!
Next.jsもmicroCMSも公式のチュートリアルやブログが充実しており、導入もしやすいと思いました。
チュートリアルが終わったけど何をすればいいかわからない。という人はぜひ自分のブログページを作ってみてください。