【Next.js】next-runtime使いませんか？

## はじめに

`Next.js`は圧倒的な人気を誇るReactのフレームワークだと思います。
僕も愛用者です。

でも、一つだけ不満があります。

それは、**サーバーサイドのロジックが書きにくいこと**。

今回はNext.jsにおけるサーバーサイドの処理を劇的に変えてくれる`next-runtime`というライブラリについて話します。

## そもそもNext.jsにおけるサーバーサイドとは？

Next.jsでサーバーサイドの処理を行う部分は主に3つあります。

### 1. getServerSideProps（getStaticProps）

SSR、SG、ISRの際にサーバーサイドで動く処理を書くことができます。
データをフェッチしてクライアントサイドにpropsとして渡すことができたりします。

```tsx
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を使っていると以下のようなコードを書く必要が出てきます。

```tsx
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`というライブラリを紹介します。

https://github.com/smeijer/next-runtime

### 処理がまとめられる

next-runtimeでは、getServerSidePropsを拡張し、get、post等のRestfulなエンドポイントを生やしてくれます。

```tsx
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しないといけないという制約を取り払うものです。

```tsx
export const getServerSideProps = handle({
  async get({ req }) {
    if (!req.user) {
      return redirect('/login');
    }

    return json({ user: req.user });
  },
});
```

このように、getServerSidePropsの中でreturnをしてしまうと、どんどん処理が肥大化していってしまいます。

```tsx
export const getServerSideProps = handle({
  async get({ req }) {
    assertIsAuthenticated(req); // isAuthenticatedでなかった場合はloginへリダイレクトしてくれる関数
    return json({ user: req.user });
  },
});
```

このような形でisAuthenticatedでなかった場合はリダイレクト処理までしてくれる関数があると良いですよね

next-runtimeでは、この`assertIsAuthenticated`をresponse throwingという概念で解決してくれています。

名の通り、レスポンスをthrowしてしまうのです。

```tsx
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してしまうという方法を取っています。

:::note
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とマージされて変えるという特性を持っていたりします。（注意は必要ですが、かなり便利です）

```tsx
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](https://remix.run/)から着想しています。
Remix使いたいけど、プロダクションだと、Next.jsの方がサポートも厚いしいいよなぁ、、と思っている方は是非使ってみてください。