SSGにNext.js以外を使わなくてもいい時代に、私は、Vikeを使いたいのです
「なぜSSGにVikeを使うのか」というタイトルにしたかったんですが、ゼクシィのCM構文が流行っているような気がして、乗っかってみました。あの雑誌を読んだことがないので、どういった層をターゲットに作られているのかかなり気になっています。
Vikeについてここでは紹介しないので、適宜 https://vike.dev を見てください。いつの間にか vite-plugin-ssr が名前を変えてマイティ・ソーになっていました。
なぜSSGにVikeを使うのか
簡素
よくある Next.js is too much for us というモチベです。
fetchのキャッシュまで望んでいなかったり、案外作りたいものはfile based routingさえ枠組みがあれば他は自分で組みたいし、複雑なものは提供してほしくない気持ちがあります。
ただ、Next.jsを使うならば、なるべくNext.jsのやり方に従っておきたい気持ちもあります。SSG(SG)に関しては、Pages Router, App Router双方で可能ですが、今になって前者を使い続ける新鮮味のなさと、後者の不安定さの板挟みという感じでした。後述しますが、全部Next.jsのやり方に従うとSG程度でもそこそこの規模のWebプロジェクトになることもあり、他の策を求めていました。
これは Meguroes リニューアルの裏側 で触れていますが、もちろんVike以外も検討しています。
例えば、11tyは筋の良いライブラリと思ったんですが、テンプレートエンジン次第でビルド部分の設定がかなり複雑になりそうで諦めました。
Gatsbyは安定こそしているものの、GraphQLサーバーがくっついてくることから、Next.js同様にオーバースペック感が否めませんでした。
VikeはViteのプラグインとして作られているので、vite.config.jsに少し変更を加えるだけでSSGできます。
import { defineConfig } from "vite";
import vike from "vike/plugin";
export default defineConfig({
plugins: [vike({ prerender: true })],
});
一度 esbuild でローカルをESM使って開発する体験を経てしまうと離れられないかつ、設定をごちゃごちゃと書きたくない自分にとってはかなり嬉しいです。
また、サーバーはNode.jsに限らず選べるようになっており、もしGraphQLがほしいなどあれば後からでも付け足せます。
ただ、デフォルトは Node.js で、Next.js のような API Routes(Route Handlers) もついていないような、非常に簡素なものとなっています。
SSR部分に手を加えやすい
これは SSR を隠蔽したい人には刺さらないです。
僕にとっては、Vike で機能を削った Next.js Pages Router の SG モードのようなものを目指して、HTML に注入するデータの形をいじったり、pre-rendering 時の処理を変えたりしたかったので、非常に手が加えやすいもので良いです。
例えばPreactで使う際は、以下のように +onRenderHtml.ts で renderToString を使い、onRenderClient.ts でHydrationを行います。
/** onRenderHtml.ts */
const onRenderHtml: OnRenderHtmlAsync = async (
pageContext,
): ReturnType<OnRenderHtmlAsync> => {
const { Page, pageProps } = pageContext;
if (!Page)
throw new Error("My render() hook expects pageContext.Page to be defined");
const pageHtml = renderToString(
<Providers pageContext={pageContext}>
<Page {...pageProps} />
</Providers>,
);
// +data.tsでfetchingしたものをここでHTMLに注入する。
const title = pageContext.data?.title || DEFAULT_TITLE;
const documentHtml = escapeInject`<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.svg" />
<title>${title}</title>
</head>
<body>
<div id="root">${dangerouslySkipEscape(pageHtml)}</div>
</body>
</html>`;
return {
documentHtml,
pageContext: {},
};
};
// ---
/** onRenderClient.ts */
const onRenderClient: OnRenderClientAsync = async (
pageContext,
): ReturnType<OnRenderClientAsync> => {
const { Page, pageProps } = pageContext;
if (!Page)
throw new Error(
"Client-side render() hook expects pageContext.Page to be defined",
);
const root = document.getElementById("root");
if (!root) throw new Error("DOM element #root not found");
hydrate(
<Providers pageContext={pageContext}>
<Page {...pageProps} />
</Providers>,
root,
);
document.title = pageContext.data?.title || DEFAULT_TITLE;
};
また、僕は以下のように、Next.js Pages Routerの機能をVikeの機能に落とし込んでみました(左がNext.jsで右がVikeです)。
- NextPage : +Page.tsx
- getStaticProps : +data.ts
- getStaticPaths : +onBeforePrerenderStart.ts
Vikeは使う機能を選択できるので、ここの柔軟さが良いですね。
Next.jsだと、これってCSRだっけ?SSRだっけ?それともSSGだっけ??みたいなことが起こり得るので、Vikeで余分な情報を削ぎ落とした状態で、誰でもキャッチアップしやすくなると思います。
JSXで書ける
もはや好みの世界ですが、テンプレートエンジンとして JSX 以外を使うモチベがもうありません。11tyを使わなかった理由としても述べてきました。
じゃあJSXで書けるならもう Next.js でいいじゃん?となるとは思います。
これに対しては、Reactの新たな機能をいち早く使うため、Next.jsを使うことはありますが、SGに果たしてどこまで今のReactの思想が反映できるのかなとは思ってます。
求めることは JSX を HTML に変換して、初期状態の描画さえ行えていれば後は適当に client side で少し見た目を変える程度なので、おそらく Suspense 以後の React の新機能はあまり使う場面がないでしょう。
テンプレートエンジンとして React を使う程度に収めたいという需要に、Vikeは十分応えられるものだと思います。
おわりに
Remix?Astro?知らない子ですね