こんにちは!
Another worksのみやぞんです。
今回は今月リリースした本テックブログについて説明します。
Headless CMS(Newt) + Next.js
このテックブログは、Headless CMSのNewtとNext.jsで構成されています。
サービスLPやコーポレートサイトでも利用しているSTUDIOが実は最有力だったのですが、シンタックスハイライトに対応していないところが唯一の欠点でした。
Headless CMSは過去にmicroCMSをサービスLPで利用したこともあり、バックエンドのコードやDBを保つ必要がなく、フロントエンドも自由に実装ができる点が魅力的で気に入っています。
今回、microCMSではなくNewtを選定した理由は無料枠が大きい。これに尽きます。
またNext.js製のテンプレートが用意されており、デザインから考えなくてもシンプルなカードビューレイアウトや検索機能、タグ機能が利用できるようになっている点も「とりあえず立ち上げたい」という人にとって嬉しいポイントです。
シンタックスハイライト
STUDIOを採用することができなかった理由の一つがこのシンタックスハイライト機能になります。
これはNewtのテンプレートにも含まれていないため、自前で実装を行います。
import cheerio from "cheerio";
import hljs from "highlight.js";
import "highlight.js/styles/github-dark.css";
const body = useMemo(() => {
const cheerRoot = cheerio.load(currentArticle.body);
cheerRoot("pre code").each((_, elm) => {
const result = hljs.highlightAuto(cheerRoot(elm).text());
cheerRoot(elm).html(result.value);
cheerRoot(elm).addClass("hljs");
});
if (currentArticle?.body) {
return {
__html: cheerRoot.html(),
};
}
return {
__html: "",
};
}, [currentArticle?.body]);
CheerioによりcurrentArticle.body(Newtから送られてくる記事のDOM情報)をパースし、hljsによってスタイリングを行っています。
とにかく早く構築したいので、便利なライブラリたちに頼りました。
On-Demand-ISR
ブログ構築の際に考えたいのが、記事更新と再ビルドの関係についてです。
Next.jsのSGやISRなどのレンダリング方法については以下の記事に書いておりますので、是非参考にしてください。
SGは静的ページの生成にぴったりなものの、ブログであるとWebHookを使って記事更新ごとにビルドしないといけないことがネックになります。(複数人が同時に実行するとスタックが生じる)
またISRはSSRとSGのいいとこ取りをした素晴らしい機能ですが、キャッシュ更新感覚を固定で設定する必要があるので、記事更新後に内容が即時反映されなかったりなどの煩わしさがあります。
そこで便利なのが、今年8月に晴れて安定版になったのがNext.jsのOn-demand ISRの機能です。
- これまでのISRは
revalidate
で指定した時間が経過されるまでは、キャッシュされた情報が返されるので、データを更新してもすぐ反映されなかったが、On-demand ISRは任意のタイミングででキャッシュのパージとページの再生成ができる - APIルートで、
revalidate
を実行するとgetStaticProps
を使用する個々のページのキャッシュを再生成する
実装例はこちら。
// pages/api/revalidate.ts
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.query.secret !== process.env.REVALIDATE_SECRET_TOKEN) {
return res.status(401).json({ message: 'Invalid token' });
}
try {
const { slug } = req.body;
await res.revalidate(`/articles/${slug}`);
return res.json({ revalidated: true });
} catch (err) {
return res.status(500).send('Error revalidating');
}
これでhttps://[domain]/api/revalidate
を外部から叩くことで、任意のタイミングでキャッシュをパージすることができます。
後はこのURLをNewtのWebHook機能で記事更新などのタイミングで実行するように設定をすれば、記事更新ごとにデプロイすることなく内容が反映されます。
Sitemap + Search Console
ブログを公開したら、検索結果に反映されるようにクローラに検索有効なページを教えて上げる必要があります。
それがサイトマップと呼ばれるものですが、これが生成できるように設定をしましょう。
// pages/sitemap.xml.tsx
import { GetServerSidePropsContext } from "next";
import { fetchArticles } from "../lib/api";
export const getServerSideProps = async ({
res,
}: GetServerSidePropsContext) => {
const xml = await generateSitemapXml();
res.statusCode = 200;
res.setHeader("Cache-Control", "s-maxage=86400, stale-while-revalidate");
res.setHeader("Content-Type", "text/xml");
res.end(xml);
return {
props: {},
};
};
async function generateSitemapXml() {
let xml = `<?xml version="1.0" encoding="UTF-8"?>`;
xml += `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`;
xml += `<url>
<loc>https://[domain]/</loc>
<changefreq>weekly</changefreq>
</url>
`;
const { articles } = await fetchArticles();
articles.forEach((article) => {
xml += `
<url>
<loc>https://[domain]/article/${article.slug}</loc>
<changefreq>weekly</changefreq>
</url>
`;
});
xml += `</urlset>`;
return xml;
}
const Page = () => null;
export default Page;
SSRで生成することでクローラに対応します。
このページはhttps://[domain]/sitemap.xml
になるので、これをGoogle Search Consoleに登録して完了です。
参考:Google Search Consoleへのサイトマップの登録
GTM + GA
テックブログの運用はエンジニアの工数で成り立っているので、その工数により得られた成果が可視化できるようにGTMやGAを導入しました。
Twitter, オーガニックなどどこからの流入であるか、記事公開後の反響などを分析しましょう。
参考:Next.jsでのGTMやGAの導入方法
さいごに
いかがでしたでしょうか。
今回はNext.jsによる弊社のテックブログの構築についてお話しました。
テックブログの構築によって、ついでにOn-demand ISRについて実際に触れることができたことは良かったなと感じています。
みなさんもぜひ使ってみてください!
Share this post