📌

Next13/NextAuth/PrismaのmonorepoでPrismaClientのエラーにハマり込む

2023/07/26に公開

エラー内容

Unhandled Runtime Error
Error: PrismaClient is unable to be run in the browser.
In case this error is unexpected for you, please report it in https://github.com/prisma/prisma/issues

PrismaClientはブラウザで動きません、とな

構成

ディレクトリ構成

packages 
  - auth (NextAuth) ... 認証系の一元管理
  - db (Prisma) ... DB関係
apps 
  - web (Next13) ... アプリケーション本体

package.json (抜粋)

"dependencies": {
  "next": "^13.4.12",
  "next-auth": "^4.22.3",
  "prisma": "^5.0.0"
}

背景

create-t3-turboをもとに簡単なサービスを作ろうとしている。
もともと auth というワークスペースがあるので、next-auth 関係はこいつに一元管理しようとしたところぶっ飛んだ。

障害発生

やったことは、packages/auth/index.ts にexportする必要のあるものを詰め込んだだけ。
ほんとにそれだけなんです😢

▼ packages/auth/index.ts

export { default } from "next-auth";
export { signIn, signOut, useSession } from "next-auth/react";
export { authOptions } from "./src/auth-options";
export { getServerSession } from "./src/get-session";
export type { Session } from "next-auth";

こうすれば、next-auth のバージョン管理が一元できるし、ファイルも無駄に増えない。
利用側はここからimportするだけ、と画策。
で起動すると冒頭のエラーが。
わかる人には「やばそー」って分かるのかな。

原因

NextAuthOptions の定義にprismaを利用しているところがあるが、これがフロント側でimportされてしまっていたのが原因。

下記のような定義(ありがちな内容)

export const authOptions: NextAuthOptions = {
  callbacks: {
    session({ session, user }) {
      if (session.user) {
        session.user.id = user.id;
      }
      return session;
    },
  },
  adapter: PrismaAdapter(prisma),
  providers: [
    GithubProvider({
      clientId: env.GITHUB_ID,
      clientSecret: env.GITHUB_SECRET,
    }),
  ],
};

一口に「importされてしまっていた」と言っても、packages/auth/index.ts を介して勝手に呼び出されてしまってた感じ。
すごく発見しづらかった。

具体的に示すと、

▼ apps/web/src/components/auth.tsx

import { signIn, signOut, useSession } from "@acme/auth";
export const AuthButton = () => {
  const { data: session } = useSession();
  if (session) {
~~~

こんな感じでコンポーネントをImportすると👇(インラインで

▼ packages/auth/index.ts

export { default } from "next-auth"; 
export { signIn, signOut, useSession } from "next-auth/react"; // <- こいつをimportしたら
export { authOptions } from "./src/auth-options"; // <- これが付き添いで来てエラー
export { getServerSession } from "./src/get-session";
export type { Session } from "next-auth";

という状況。
NextAuthOptions 自体はフロント側から呼び出されることがあるみたいなのでタカをくくっていたが、ダメだったらしい。

改善

packages/auth を client.ts/server.ts に分けて改善した。
具体的には、フロントから呼び出されてるかサーバー側から呼び出されているか、利用側から逆算して判断した。
ついでにtypesも別にした。

▼ packages/auth/client.ts

export { default } from "next-auth";
export { SessionProvider, signIn, signOut, useSession } from "next-auth/react";

▼ packages/auth/server.ts

export { authOptions } from "./src/auth-options";
export { getServerSession } from "./src/get-session";

▼ packages/auth/type.d.ts

export type { Session } from "next-auth";

▼ packages/auth/package.json

  "files": [
    "client.ts",
    "server.ts",
    "type.d.ts"
  ],

で、Import側も追従。

▼ apps/web/src/components/auth.tsx

import { signIn, signOut, useSession } from "@acme/auth/client"; // <-
export const AuthButton = () => {
  const { data: session } = useSession();
  if (session) {
~~~

まとめ

楽だからってindex.tsで全部一元管理してexportすると飛ぶぞ!
Next13では、Client側なのかServer側なのか各々明確に判断して分けるべき!

蛇足

Next13普及してくと、このあたりの調査がかなりしんどそう。😢
必要なことなんだけどね。

Discussion