ymmr
Recent Articles
    公開日: 2024/03/16

    【Next.js】Middleware を特定のページでは実行しない方法

    Next.js におけるミドルウェアとは、サーバーにリクエストが飛んだタイミングで何らかの処理を実行できる仕組みのことです。ポートフォリオの作成にあたり、特定のページを除いて NextAuth.js で認証する方法を学びました。今回はその備忘録となります。

    Middleware の動かし方

    プロジェクトのルート (src ディレクトリなど) に middleware.ts ファイルを作成して、ミドルウェアをエクスポートします。あとは Vercel などのサーバーにデプロイすることで、CDN の Edge 上でミドルウェアが動作します。

    NextAuth.js withAuth というミドルウェアを提供しているため、そのまま利用するだけでサイト内のページを保護できます。

    middleware.ts
    export { default } from 'next-auth/middleware';
    
    // `/protected` 以下のリソースには、認証されたユーザーのみアクセスできる。
    export const config = {
      matcher: ['/protected/(.*)'],
    };
    

    注目していただきたいのは config オブジェクトの部分です。matcher に正規表現を指定することで、認証を要求するページを限定しています。

    特定のページのみ、認証なしでアクセスを許可する

    今回やりたかったことは、上記の例とは逆に特定のページだけアクセスを許可することです。方法は公式ドキュメントで紹介されていました。

    どうやら matcher の判定には path-to-regexp というライブラリを使用している様なので、その実行結果を検証することで意図した正規表現が組めているか確認できそうです。

    正規表現をカスタマイズする

    検証しながら正規表現をカスタマイズするため、Jest で path-to-regexp を動かしてみます。

    import { pathToRegexp } from 'path-to-regexp';
    
    test('regexp for matcher in middleware.ts', () => {
      // パスと期待値のリスト
      // test が true となれば、そのページでミドルウェアが実行される。
      const expected = [
        { path: '/', test: false },
        { path: '/about', test: false },
        { path: '/protected', test: true },
        { path: '/protected/foo', test: true },
      ];
    
      // matcher に使う正規表現
      const regexp = pathToRegexp('/protected(/?.*)');
    
      const paths = expected.map(({ path }) => path);
      const result = paths.map((path) => ({
        path,
        test: regexp.test(path),
      }));
    
      expect(result).toEqual(expected);
    });
    
    
    

    テストが通っているので、少なくともリストに挙げたページではミドルウェアが期待通りに実行される (されない) ことが保証されます。続いて、特定のページを除いてミドルウェアを実行する正規表現を検証します。

    import { pathToRegexp } from 'path-to-regexp';
    
    test('regexp for matcher in middleware.ts', () => {
      const expected = [
        { path: '/', test: true },
        { path: '/protected', test: true },
        { path: '/articles/foo', test: true },
        { path: '/articles/published', test: false },
        { path: '/articles/published/bar', test: false },
      ];
    
      // `articles/published` 以下のページにはマッチしない正規表現
      const regexp = pathToRegexp('/((?!articles/published).*)');
    
      const paths = expected.map(({ path }) => path);
      const result = paths.map((path) => ({
        path,
        test: regexp.test(path),
      }));
    
      expect(result).toEqual(expected);
    });
    

    正規表現の (?!...) の部分は negative lookahead assertion という構文を使用しています。上記の例でいうと、絶対パス / を検索しますが articles/published が後ろに続くとマッチしません。

    おわりに

    今回はミドルウェアを実行するページをコントロールする方法を紹介しました。対象のとなるページはこのような方法で検証するとよいかもしれません。

    参考

    How to exclude home page (index.js) in nextJS middleware matcher? · vercel/next.js · Discussion #48913Summary I have a middleware file in nextJS. I don't want the middleware to run on these 4 different paths (pages): home page about page contact page sales page The middleware should run on every ot...github.com