【Next.js】unstable_runtimejsでhydrationを無効化し静的(MPA)化する
やりたいこと
Next.jsでSSG(Static Export)なブログを作成していると、サイト自体を完全に静的化したい時がある。
「完全な静的化」とは
どいうことかというと、Next.jsではSSGをしてもhydration処理のためにruntime-jsが実行される。例えHooksや<Link>
タグを利用したclient-side routingを利用していなくとも。
そのため上のようなjsファイルが読み込まれるわけだが、async
が指定されているとはいえサイズ的にはかなり膨大。
そこでNext.jsのJavaScriptを全て抜いて完全にMPAな(ピュアなHTMLのみの)サイトにしたい。
そもそもMPA化したい理由
- 初回アクセス時の表示速度を上げたい
- ブログの性質上、検索結果への直帰率が高いためclient-side routingが不要(SPAにする恩恵が少ない)
Next.jsを使わない手もある
ちなみに別のフレームワークやテンプレートエンジンを利用するという方法もあったが、下記の理由から断念した。
- tsxで書きたいのでEJSやPUGは候補外
- Reactを利用できるMPAフレームワークのAstroにも興味はあったが、単純に乗り換える手間がかかる
解決法
Next.js 12以下の場合
import Layout from "../layout";
+ export const config = {
+ unstable_runtimeJS: false,
+ };
+
const ExamplePage = () => {
return (
<Layout>
<h1>Hello world</h1>
</Layout>
);
};
export default ExamplePage;
/pages
を利用している場合、静的化したいページのunstable_runtimeJS
をfalse
にするだけで実現できる。
Next.js 13以上(App Directory)の場合
App Directoryの場合unstable_runtimeJS
は使用できず、なおかつNext.js側でそういったオプションは用意されていない。
そこで、static export
で生成したHTMLから直接script
タグを削除する、という無理やりな方法で実現することにした。
1. npm-scriptsを実装
#! /usr/bin/env node
const path = require("path");
const fs = require("fs");
const { JSDOM } = require("jsdom");
const HTML_FILE_PATH = "out";
const FileType = {
File: "file",
Directory: "directory",
Unknown: "unknown",
};
/**
* ファイルの種類を取得する
* @param {string} path パス
* @return {FileType} ファイルの種類
*/
const getFileType = (path) => {
try {
const stat = fs.statSync(path);
switch (true) {
case stat.isFile():
return FileType.File;
case stat.isDirectory():
return FileType.Directory;
default:
return FileType.Unknown;
}
} catch (e) {
return FileType.Unknown;
}
};
/**
* 指定したディレクトリ配下のすべてのファイルをリストアップする
* @param {string} dirPath 検索するディレクトリのパス
* @return {string[]}
*/
const getHtmlFilePaths = (dirPath) => {
const result = [];
const paths = fs.readdirSync(dirPath);
paths.forEach((a) => {
const path = `${dirPath}/${a}`;
switch (getFileType(path)) {
case FileType.File:
if (/^.+.html$/.test(path)) result.push(path);
break;
case FileType.Directory:
result.push(...getHtmlFilePaths(path));
break;
default:
}
});
return result;
};
const dirPath = path.join(process.cwd(), HTML_FILE_PATH);
const htmlFilePaths = getHtmlFilePaths(dirPath);
htmlFilePaths.forEach((htmlFilePath) => {
const html = fs.readFileSync(htmlFilePath, "utf-8");
const document = new JSDOM(html).window.document;
// scriptタグの削除
document.querySelectorAll("script").forEach((script) => {
script.remove();
});
fs.writeFileSync(htmlFilePath, document.documentElement.outerHTML);
});
/bin/optimizeHtmlFile/index.js
のようなファイルを作る。
/out
下に生成されたHTMLファイルを全て取得- DOM操作ができる
jsdom
ライブラリを使いscript
タグを削除 - HTMLファイルとして再書き出し
2. build後に実行するよう設定
"scripts": {
"dev": "next dev",
- "build": "next build",
+ "build": "next build && npm run oh",
+ "oh": "bin/optimizeHtmlFile/index.js",
あとはbuild
後にそのnpm-scriptsを実行するようにpackage.json
に追記する。これでruntime-jsを全て無効化できた 🎉