はじめに
みなさん、こんにちは!C言語エンジニア(Web開発は未経験)からフロントエンドエンジニアを目指しているともぼーと言います!
現在はNext.js勉強中で、技術ブログを初めて作成したのでその作業記録をまとめました。
今回作成した技術ブログ
こちらが今回作成した技術ブログサイトです。
私が投稿してきたQiitaの記事と、microCMSに登録した仮のブログ記事を見れるようになっています。
技術スタック
- 言語
- React
- Typescript
- フレームワーク
- Next.js
- フロントエンド
- Tailwindcss
- daisyUI
- バックエンド
- firebase
SSG・SSRとは?
今回、初めてNext.jsでブログ記事を開発した上で SSG・SSR というレンダリング手法を用いて実装しました。
SSGとは
サーバー側で ビルド
時にHTMLを生成するレンダリング手法の一つです。ユーザーからのリクエスト時には、既に完成しているHTMLをサーバーから取得するため、表示が速いことが利点です。ただ、ビルド
時に生成されたHTMLからは変化しないため、内容を更新するためには再度 ビルド
が必要になります。
SSRとは
SSGとは異なり、クライアントからのリクエスト時にサーバー側でHTMLを生成し、クライアントにHTMLを送信する手法です。SSG は更新された情報を反映できないのですが、SSR はリクエスト時に毎回HTMLを生成し直すので最新の情報を取得することができます。ただ、毎回レンダリングするのでSSG よりは表示が遅くなります。
まだ SSG・SSR について何?となっている方はこちらを参考にすると分かりやすいかと思います。
それでは、今回の作業をまとめていきましょう。
microCMSにコンテンツを設定する
今回作成したのは技術ブログサイトで、microCMSに登録したブログ記事を表示するようなイメージです。
microCMSにログインしたらまずはAPIのエンドポイントを作成します。
このURLに対してリクエストすることで、登録したコンテンツがJson形式で取得できます。
続いてmicroCMSには「記事のタイトル、記事のURL、記事のサムネ」を登録します。
QiitaとmicroCMSと連携したページを作成する
microCMSに登録したコンテンツを取得するAPIを作成します。
設定したAPIエンドポイントに対して GET
リクエストで取得します。
route.ts
は app/api/blogs 配下に作成し、Next.jsのフレームワーク機能を使用してAPIエンドポイントとして呼び出せるようにします。
import { MicrocmsArticle } from "@/app/types";
import { NextResponse } from "next/server";
export async function GET() {
console.log("API route accessed."); // APIルートへのアクセスをログに出力
try {
const apiUrl = `https://gc0pp0jj7k.microcms.io/api/v1/posts`;
const apiKey = process.env.NEXT_PUBLIC_MICROCMS_API_KEY;
const response = await fetch(apiUrl, {
headers: {
"X-MICROCMS-API-KEY": apiKey || "",
},
});
const data = await response.json();
const articles: MicrocmsArticle[] = data.contents;
return NextResponse.json(articles, { status: 200 });
} catch (error) {
console.error("An unexpected error occurred:", error);
const errorMessage = error instanceof Error ? error.message : String(error);
return NextResponse.json(
{ error: `An unexpected error occurred: ${errorMessage}` },
{ status: 500 }
);
}
}
Qiita記事も異なるパスで同様にAPIエンドポイントを作成しますが、長くなるのでここでは省略します。
そして、 QiitaとmicroCMS のAPIを呼び出し、それぞれの記事を表示する page.tsx
を作成します。
import ArticleCard from "./coponents/ArticleCard";
import ViewAllArticlesButton from "./coponents/ViewAllArticlesButton";
import { ArticleType, MicrocmsArticle, QiitaArticle } from "./types";
export default async function Home() {
const thumbnail =
"https://qiita-user-contents.imgix.net/https%3A%2F%2Fcdn.qiita.com%2Fassets%2Fpublic%2Farticle-ogp-background-9f5428127621718a910c8b63951390ad.png?ixlib=rb-4.0.0&w=1200&mark64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTkxNiZoPTMzNiZ0eHQ9SmF2YVNjcmlwdCVFMyU4MSVBN1VSTCVFMyU4MSU4QiVFMyU4MiU4OU9HUCVFNSU4RiU5NiVFNSVCRSU5NyVFMyU4MSU5OSVFMyU4MiU4QiZ0eHQtY29sb3I9JTIzMjEyMTIxJnR4dC1mb250PUhpcmFnaW5vJTIwU2FucyUyMFc2JnR4dC1zaXplPTU2JnR4dC1jbGlwPWVsbGlwc2lzJnR4dC1hbGlnbj1sZWZ0JTJDdG9wJnM9NDM5YjY5NjY3Nzg3ZTExYzdmYTM2YjI1ZDg3NTcyN2Y&mark-x=142&mark-y=112&blend64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTYxNiZ0eHQ9JTQwa3N5dW5ubm4mdHh0LWNvbG9yPSUyMzIxMjEyMSZ0eHQtZm9udD1IaXJhZ2lubyUyMFNhbnMlMjBXNiZ0eHQtc2l6ZT0zNiZ0eHQtYWxpZ249bGVmdCUyQ3RvcCZzPWUxMjJhOTA1NDdiNTMzNDI4MWY3YmU0M2U2Y2I1M2Rh&blend-x=142&blend-y=491&blend-mode=normal&s=1a611f7e8833ff640580434a1b03d27a";
const urlParams = new URLSearchParams();
urlParams.append("per_page", "4");
const APP_URL = process.env.NEXT_PUBLIC_APP_URL;
const qiitaResponse = await fetch(`${APP_URL}/api?${urlParams.toString()}`, {
cache: "no-store",
});
const qiitaArticles = await qiitaResponse.json();
const microcmsResponse = await fetch(`${APP_URL}/api/blogs`);
const blogArticles = await microcmsResponse.json();
return (
<div className="px-20 py-10 flex flex-col gap-10">
<div>
<div className="py-6 flex justify-between items-center px-4">
<p className="text-3xl text-gray-400">個人記事</p>
<ViewAllArticlesButton type={ArticleType.Qiita} />
</div>
<div className="flex gap-4 justify-center p-4">
{qiitaArticles ? (
qiitaArticles.map((article: QiitaArticle) => (
<ArticleCard
key={article.id}
title={article.title}
date={article.updated_at}
url={article.url}
thumbnail={thumbnail}
/>
))
) : (
<p>Loading...</p>
)}
</div>
</div>
<div>
<div className="py-6 flex justify-between items-center px-4">
<p className="text-3xl text-gray-400">ブログ記事</p>
<ViewAllArticlesButton type={ArticleType.Microcms} />
</div>
<div className="flex gap-4 justify-center p-4">
{blogArticles.length > 0 ? (
blogArticles.map((article: MicrocmsArticle) => (
<ArticleCard
key={article.id}
title={article.title}
date={article.publishedAt}
url={article.url}
thumbnail={article.thumbnail.url}
/>
))
) : (
<p>Loading...</p>
)}
</div>
</div>
</div>
);
}
これで Qiitaの記事 と micorCMSのブログ記事 を表示することができました。
Next.jsで開発して学んだこと
Reactだけで開発していた頃と比べて、Next.jsではフレームワークとしての「ルール」 を守る必要があると強く感じました。フォルダ構成やAPIリクエストの方法など、決まりごとを理解するところからスタートする必要があり、最初は戸惑いました。
さらに、クライアントサイドだけを意識していればよかったReactと違い、Next.jsではサーバーサイドとクライアントサイドの使い分けが重要になります。加えて、SSR・SSG・CSR・ISR といった複数のレンダリング手法をページの内容に応じて選択する必要があり、開発の複雑さが一段と増したと実感しました。
その一方で、Next.jsはReactでは実現しづらかったSEO対策や、ユーザーの操作に応じた効果的な処理を可能にしてくれます。苦労もありますが、それ以上に得られるメリットは大きいと感じています。
おわりに
今後は、Next.jsをさらに使いこなすために個人開発で実践を重ね、収益化を目指せるレベルの開発に挑戦していきたいです。
参考
JISOUのメンバー募集中!
プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか?
興味のある方は、ぜひホームページをのぞいてみてくださ!
▼▼▼