Next.js の Middleware を使ってサクッと迷惑 Bot たちを追い払う

こんにちは。ぬこすけです。

皆さんは Bot のアクセスに悩まされたことはありますか？

**私はあります。**

[私が個人開発しているサイト](https://nuko-programming.com/)でも Bot からガンガンアクセスがきます。
![スクリーンショット 2022-08-18 22.25.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/dfec9cf9-ca65-cc0b-fce9-462dcd56aca1.png)

1ページリクエストすると画像や js ファイルなどの追加のリクエストも走るのでやっかいです。
私のサイトは `Cloud Run` で動かしていますが、リクエストに比例して料金が加算するようにしているので、 Bot からの余計なリクエストを抑えてお財布に優しくしたいところです。

一方で、 `Next.js` のバージョン 12.2 で `Middleware` という機能が安定版としてリリースされました。

https://nextjs.org/docs/advanced-features/middleware

この `Middleware` という機能を使えば、 **ユーザーからのリクエストが来た時にページを返却する前にコードを実行することができます**。
例えば、ユーザーエージェントを判定してリダイレクトをしたり、Basic認証をかけたり、みたいなことができます。

**今回は `Middleware` を使ってこの迷惑 Bot たちを追い払います**。

やることは簡単です。

1. `public` ディレクトリ配下に Bot 用の html ファイルを用意しておく。
2. プロジェクトのルート配下に `middleware.js（ts）` を置いて 20 行くらいコード書く。

はい、終わりです。
するとこんな感じでステータスコード 403（Forbidden） で返却することができます。

![nukopro_bot_access_log.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/96d8472c-0bb1-5324-e75d-cb32f1646f8f.png)

詳しく説明していきます。

## 1. `public` ディレクトリ配下に Bot 用の html ファイルを用意しておく。

Bot 向けに表示する html ファイルを用意します。
例えば次のような `403.html` を用意して `public` 配下に置きます。

```403.html
<html lang='ja'>
  <head>
    <title>Forbidden</title>
    <meta name='robots' content='noindex' />
  </head>
  <body>
    <p>
      Forbidden
    </p>
  </body>
</html>
```

Bot 相手なので凝った html を用意しなくて大丈夫でしょう。

## 2. プロジェクトのルート配下に `middleware.js（ts）` を置いて 20 行くらいコード書く。

`Next.js` ではプロジェクトのルートディレクト直下に `middleware.js` （ TypeScript なら `middleware.ts` ）を置くことで `Middleware` 機能を使うことができます。
次のような `middleware.js` を用意しましょう。

```middleware.js
import { NextResponse, userAgent } from 'next/server';

// 追い払いたい Bot たち
const uaBlackList = Object.freeze([
  'PetalBot',
  'SemrushBot',
]);

export function middleware(req) {
  const { ua } = userAgent(req);
  // robot.txt へのアクセスは除く
  if (
    uaBlackList.some((blackUa) => ua.indexOf(blackUa) > -1) &&
    req.nextUrl.pathname !== '/robots.txt'
  ) {
    req.nextUrl.pathname = '/403.html';
    return NextResponse.rewrite(req.nextUrl, { status: 403 });
  }
  return NextResponse.next();
}
```

この `middleware.js` では予め定義したブラックリストに含まれる Bot からアクセスが来た時は `403.html` を表示し、それ以外の場合は通常通りページを表示します。

あとは本番環境にデプロイして終わりです。
ユーザーエージェントを Bot に偽装してページにアクセスすると次のような画面が表示されます。

![スクリーンショット 2022-08-18 22.16.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/e35e9b9d-13d9-9104-556c-68b64ac27fc4.png)

### [余談] html じゃなくて `Next.js` のカスタムエラーページを表示しないの？

余談なので読み飛ばしても大丈夫です。

`Next.js` には `pages/404.jsx` のようにカスタムエラーページを表示する機能がありますが、**これは使いません**。

というのも、**カスタムエラーページを表示した場合、さらに余計なリクエストが `middleware` に来てしまうことがあるから** です。

例えば全ページ共通の `_document.jsx` に次のような jsx が定義されていたとします。

```_document.jsx
export default function Document() {
  return (
    <Html lang='ja'>
      <Head>
        ...
        <link rel='icon' href='/images/icons/icon-16x16.png' sizes='16x16' type='image/png' />
        ...
      </Head>
      ...
    </Html>
  );
}
```

この場合、 Bot からアクセスが来て `pages/404.jsx` を表示しても、さらに `/images/icons/icon-16x16.png` へ追加のリクエストが飛んできます。
できれば、余計な Bot からはリクエストを抑えたいので `403.html` のような静的なファイルを用意しています。

## まとめ

1. `public` ディレクトリ配下に Bot 用の html ファイルを用意しておく。
2. プロジェクトのルート配下に `middleware.js（ts）` を置いて 20 行くらいコード書く。

これだけでサクッと Bot からの余計なアクセスを抑えることができました。

`Next.js` で Bot のアクセス対策をしたい時はぜひ参考にしてみてください！