はじめに
Next.js
は圧倒的な人気を誇るReactのフレームワークだと思います。
僕も愛用者です。
でも、一つだけ不満があります。
それは、サーバーサイドのロジックが書きにくいこと。
今回はNext.jsにおけるサーバーサイドの処理を劇的に変えてくれるnext-runtime
というライブラリについて話します。
そもそもNext.jsにおけるサーバーサイドとは?
Next.jsでサーバーサイドの処理を行う部分は主に3つあります。
1. getServerSideProps(getStaticProps)
SSR、SG、ISRの際にサーバーサイドで動く処理を書くことができます。
データをフェッチしてクライアントサイドにpropsとして渡すことができたりします。
export async function getServerSideProps(context) {
return {
props: {
name: 'qiitan'
},
}
}
2. API Route
pages/api
にファイルをおくとAPI Routeを生成してくれます。
主にクライアントサイドからfetch
で叩き、データをGETしたりPOSTしたりできます。
3. Middleware
getServerSidePropsより前に動くサーバーサイドの処理です。
Vercelにデプロイすると、オリジンサーバーに到達する前に、エッジサーバーでこの処理が動くことになります。
ABテストの振り分けやリダイレクト処理、認証・アクセスコントロールなどに使います。
何が微妙なのか?
上の3つをみると、
アクセスコントロールなど、オリジンサーバーが処理しなくても良いようなものはmiddlewareで実行し、
SSRのデータ準備はgetServerSidePropsを使う。
POSTをしたい時やクライアントサイドでデータフェッチしたい場合はAPI Routeを使う。
要件は満たせるような気がします。
でも僕は不満な点が2点あります。
1. 処理がバラける
上3つ、きちんと処理を抽象化・共通化することができれば良いですが、それぞれRequest・Responseの形式が違ったりしますし、
middlewareに至ってはエッジランタイムで動くことが期待されているため、Node.jsで使えるAPIが使えなかったりします。
なので処理の共通化が難しく、バラけるというのが現状です。
2. 一つ一つの処理が肥大化・複雑化しがち
getServerSidePropsを使っていると以下のようなコードを書く必要が出てきます。
export async function getServerSideProps(context) {
// 認証が通らなかった場合はloginへリダイレクト
if (!isAuthenticated(context)) {
return {
redirect: {
permanent: false,
destination: '/login'
}
};
}
// idを持っていなかった場合はlistページへリダイレクト
if (!context.params.id) {
return {
redirect: {
permanent: false,
destination: '/list'
}
};
}
// データをフェッチしてなかった場合は404へ
const data = await fetchProfile(context.params.id);
if (!data) {
return {
notFound: true
};
}
// データを返す
return {
props: {
name: data.name;
},
};
};
isAuthenticated
やfetchProfile
など、関数は切り分けられているのになぜか処理が長く見える。
これは、getServerSidePropsの関数内でオブジェクトをreturnしないといけないという制約があるからです。
でも、これだとどんどん肥大化していってしまいます。
next-runtimeを使う
上記二つを解消してくれるnext-runtime
というライブラリを紹介します。
処理がまとめられる
next-runtimeでは、getServerSidePropsを拡張し、get、post等のRestfulなエンドポイントを生やしてくれます。
import { handle, json } from 'next-runtime';
export const getServerSideProps = handle({
async get({ params, query }) {
return json({ name: 'Stephan Meijer' });
},
async post({ req: { body } }) {
return json({ message: 'Thanks for your submission!' });
},
});
export default function Home({ name, message }) {
if (message) {
return <p>{message}</p>;
}
return (
<form method="post" encType="multipart/form-data">
<input name="name" defaultValue={name} />
<button type="submit">submit</button>
</form>
);
}
GETもPOSTも処理が一箇所に集まり、インターフェースも統一されるので、共通化が捗ります。
Response Throwingができる
next-runtimeにはResponse Throwingという機能があります。
先ほどの、getServerSidePropsの関数内でオブジェクトをreturnしないといけないという制約を取り払うものです。
export const getServerSideProps = handle({
async get({ req }) {
if (!req.user) {
return redirect('/login');
}
return json({ user: req.user });
},
});
このように、getServerSidePropsの中でreturnをしてしまうと、どんどん処理が肥大化していってしまいます。
export const getServerSideProps = handle({
async get({ req }) {
assertIsAuthenticated(req); // isAuthenticatedでなかった場合はloginへリダイレクトしてくれる関数
return json({ user: req.user });
},
});
このような形でisAuthenticatedでなかった場合はリダイレクト処理までしてくれる関数があると良いですよね
next-runtimeでは、このassertIsAuthenticated
をresponse throwingという概念で解決してくれています。
名の通り、レスポンスをthrowしてしまうのです。
import { redirect } from 'next-runtime';
function assertIsAuthenticated(request) {
if (!user.request) {
throw redirect('/login', 303);
}
}
このようにしておくことで、isAuthenticatedでなかった場合は、throwが発生します。
JavaScriptにおけるthrowはエラーではありません。
即時にアプリケーションが落ちることはなく、try ~ catch
でthrowされたものを受け取ることができます。
next-runtimeではこのJavaScriptの機能を利用し、レスポンスをthrowしてしまうという方法を取っています。
throwされたレスポンスはnext-runtimeによりtry~catchされ、return responseされます。
なので、あたかもgetServerSideProps上でreturnしたかのように振る舞える訳ですね
その他にも、もっと共通化が捗る機能もある
next-runtimeにはmiddlewareという機能もあります。
middlewareはnext.jsのmiddlewareとは少し異なるのですが、getServerSidePropsより前に処理が実行されるという意味では同じです。
上述の通り、next-runtimeにおけるmiddlewareはgetServerSidePropsより前に起動します。
なので、アクセスコントロールを共通化して先にするということも可能ですし、共通のpropsを返したりすることもできます。
以下のようにuse
というプロパティにmiddleware関数を渡すことで実現可能で、ここでjsonを返した場合、後続のget処理のpropsとマージされて変えるという特性を持っていたりします。(注意は必要ですが、かなり便利です)
import { handle, json } from 'next-runtime';
export const getServerSideProps = handle({
use: [
() => {
return json({ propOne: 1 });
},
],
async get() {
return json({ propTwo: 2 });
},
});
まとめ
いかがでしたでしょうか?
next-runtime、いいライブラリなのにあまり伸びてないので記事にしてみました。
実はこのnext-runtimeはNext.jsと同じくReactのフレームワークであるRemixから着想しています。
Remix使いたいけど、プロダクションだと、Next.jsの方がサポートも厚いしいいよなぁ、、と思っている方は是非使ってみてください。