😺

Next.js+TypeScriptにおけるmonorepoのtips

2022/10/16に公開

最近monorepoなるものを知ったので,Next.js+TypeScriptで構築する際の詰まった所などを書き残す.

monorepoとは

monorepoについて簡単に自分の理解を示す.

  • 複数のプロジェクトを同一のレポジトリで管理できて便利
  • テストやlinterなどを統合できるからメンテも楽
  • 開発者全員が全体把握しやすい

などなど.

Projectの構築

基本的には以下のテンプレートをcloneすればいい.

https://github.com/vercel/next.js/tree/canary/examples/with-zones


2022/10/17 追記
TypeScript用テンプレートを作りました.

https://github.com/kage1020/with-zones-ts-app


基本的なディレクトリ構成図は以下.

root+
    |-blog+ // sub app
    |     |-public
    |     |-src
    |     |-.eslintrc.json
    |     |-next.config.js
    |     |-package.json
    |     |tsconfig.json
    |     ...
    |-home+ // main app
    |     |-public
    |     |-src
    |     |-.eslintrc.json
    |     |-next.config.js
    |     |-package.json
    |     |tsconfig.json
    |     ...
    |-.gitignore
    |-.env
    |-.eslintrc.json
    |-README.md
    |-package.json
    ...

特に特殊なものはない.

問題なのは.eslintrc.jsonの設定.このままVercelでデプロイしようとすると,no-html-for-link-pagesのエラーを吐く.

https://nextjs.org/docs/messages/no-html-link-for-pages

これは,sub appの中でsub appのルートとmain appのルートをnext/linkと<a>タグを使い分けているために,どっちかにしろよと怒られているためである.

sub appのindex.tsx
<Link href="/"> {/* sub appのroot */}
  <a>Go to Top</a>
</Link>

<a href="/">Go to Home</a> {/* main appのroot */}

これを解決するには,sub appでのaタグによるルート指定をmain appから継承すればいい.
Next.jsは専用のeslint-plugin-nextというrulesが存在するため,main appのrulesをsub appに継承し,sub appにおける/がmain appにおける/を指すようにする.

/blog/.eslintrc.json
{
  "extends": ["../home/eslintrc.json", "next/core-web-vitals"],
}

Vercelでbuildログを見ると

error - ESLint: Failed to load config "../home/eslintrc.json" to extend from. Referenced from: /vercel/path0/chat/.eslintrc.json

のエラーが出てるが,問題なく動いているのでとりあえず大丈夫っぽい.

また,main appのnext.config.jsでroutingを制御していることがわかるが,これはNext.jsやmonorepoだけに制限しているわけではない.

const { BLOG_URL } = process.env;

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
  async rewrites() {
    return [
      {
        source: '/:path*',
        destination: `/:path*`,
      },
      {
        source: '/blog',
        destination: `${BLOG_URL}/blog`,
      },
      {
        source: '/blog/:path*',
        destination: `${BLOG_URL}/blog/:path*`,
      },
    ];
  },
};

module.exports = nextConfig

BLOG_URLを全く関係ない別のアプリURLにしたり,NuxtやSvelteへのURLにしてもいい.
つまり,実質monorepoでなくても動く.これは以下のようなときに役立つかもしれない.

  • 開発チームが全くの別物だけど,ドメインは同じものを使いたい.つまり,レポジトリは別々だけど同じサービスの一部として開発したい.
  • monorepoだと規模が大きくてcloneがつらい.
  • 他のアプリのissueやPRを表示したくない.(labelで管理すれば...)

などなど.

おまけ

NextAuth.jsはmonorepoに対してもsessionが有効なので,各アプリ内でSessionProviderを設定していれば,main appで認証後,sub appでその認証情報を取得できます.
従って,clientIdclientSecretは1つ取得すれば十分であり,callbackもhttp://localhost:3000などroot URLに設定すれば大丈夫そうです.

Discussion