Next.jsのApp RouterをCloudflare Pagesにアップする際にcheerioをCSRにする
タイトルに要点を入れようとしたら長いタイトルになりましたがタイトルままの内容です。
現在、弊社で運営しているオウンドメディア「to-R Media」のバージョンアップを行っています。
to-R MediaはもともとはNext.jsのPage Routerで開発しておりSSGを行って静的ファイルとしてCloudflare Pagesにデプロイしておりましたが、今回のバージョンアップではNext.jsのApp Routerに変更を行いSSRでCloudflare Pagesにデプロイをする予定です。
ところが、Cloudflare Pagesにアップしたところ記事詳細ページで500エラーが頻出して動作が安定しませんでした。
Cloudflare PagesのSSRで利用されているCloudflare Workersは無料プランだとCPUの実行時間が10msと結構タイトで重い処理が含まれていると処理しきれないようです。
コードを調べてみるとcheerioを利用したコードハイライトの処理が重く10msの制限を超過していたようです。
もともとのSSRのコード
Nxet.js はデフォルトではReact Server Components
が採用されるため以下のようにSSRでレンダリングされるように実装していました。
import * as cheerio from 'cheerio';
import hljs from 'highlight.js';
export default async function CodeBlock({ article }: { article: string }) {
const $ = cheerio.load(article, null, false);
$('pre code').each((_, elm) => {
const result = hljs.highlightAuto($(elm).text());
$(elm).html(result.value);
$(elm).removeAttr('lang');
$(elm).addClass('hljs');
});
const highlightedArticle = $.html();
return (
<div
className="p-article-body"
dangerouslySetInnerHTML={{ __html: highlightedArticle }}
/>
);
}
修正したCSRのコード
CodeBlock.tsx
ではハイライト処理は行わずにHTMLをそのまま描画するにとどめます。
import CodeBlockHighlight from '@/components/CodeBlockHighlight';
export default async function CodeBlock({ article }: { article: string }) {
return (
<CodeBlockHighLight>
<div
className="p-article-body"
dangerouslySetInnerHTML={{ __html: article }}
/>
</CodeBlockHighLight>
);
}
CodeBlockHighlight.tsx
はuse client
を利用してReact Client Components
として実装してCSR時にcheerioによるコードハイライト処理が実行されるようにします。
'use client';
import * as cheerio from 'cheerio';
import hljs from 'highlight.js';
import { useEffect } from 'react';
type Props = {
children: React.ReactNode;
};
const CodeBlockHighlight = ({ children }: Props) => {
useEffect(() => {
const articleBody = document.querySelector('.p-article-body');
if (!articleBody) {
return;
}
const $ = cheerio.load(articleBody.innerHTML, null, false);
$('pre code').each((_, elm) => {
const result = hljs.highlightAuto($(elm).text());
$(elm).html(result.value);
$(elm).removeAttr('lang');
$(elm).addClass('hljs');
});
articleBody.innerHTML = $.html();
});
return <>{children}</>;
};
export default CodeBlockHighlight;
こうすることで以下のようにHTMLが描画されてからタイムラグがあってコードハイライトが適用されます。
こちらに変更したところCloudflare Pagesでも安定して表示されるようになりました。
Cloudflare PagesでApp Routerを利用する場合はCSRで大丈夫な処理は極力CSRに寄せてあげると安定するので注意してください。
Discussion
初めまして、半年前の記事に恐れ入ります。
こちらで遭遇されていたエラーですが、
Application error: a server-side exception has occurred (see the server logs for more information).
のようなメッセージだったか覚えていらっしゃいますでしょうか?
fetch周りをいじっており、ローカルのbuild/ssrでは問題ないのですがcloudflare pagesだと上記エラーになり、サーバでfetch中にタイムアウトしている可能性を考えています。
お返事いただけましたら幸いです。
@taku3 すいません、ちょっとエラーメッセージは覚えてないですね。すいません。
ご返事いただきありがとうございます。畏まりました。