🦔

Next.jsのRoute Handlers + ElysiaJSの組み合わせでスタンドアロンビルドすると挙動がおかしくなる問題

2024/12/28に公開

Problem

  • Next.jsの Route Handlers を使ってElysiaJSのルーターをフックして呼び出している
  • 開発ビルドでは何の問題もなく動作するが、スタンドアロンビルドしたものを単体で動かすと「一部のエンドポイントにPOSTしてもbodyがundefinedになる」のような奇妙な問題が発生する

route.ts

import { authRouter as server } from "@/controllers/AppController";

export const GET = server.handle;
export const POST = server.handle;

Solution

ElysiaJSは起動時間改善のためにデフォルトでAOTが有効になっています。(ドキュメントだとデフォルトでfalseになってるけど恐らく誤り?)
Next.jsのスタンドアロンビルドは、ビルド時に必要な依存関係とコードを解析してそれのみを含めることで実現されていますがこの解析が完璧かと言うとそんなことはなく、必要なファイルを含めないせいでElysiaJSのAOTを壊してしまうようです。

ElysiaJSのAOTを無効にする

ElysiaJSのインスタンス生成時に以下のようにオプションを指定してAOTを無効にして解決します。

import { Elysia, error, t } from "elysia";

export const authRouter = new Elysia({ prefix: "/auth", aot: false })
    .use(errorHandler)
    .post("/register-request", async ({body, cookie: {challengeSession}}) => {

[以下略]

注意点: AOTを無効にしても異常な部分がある場合

その他私がハマった事項を書いておきます。

  • ミドルウェア(derive)内でerrorを返すと上手く動かない

    • ミドルウェア内でエラーを返すのではなく判別可能な例外を投げるようにして、グローバルなエラーハンドラー内で適切なステータスコードを返すことで回避できました
  • 正直ここまでしてスタンドアロンビルドにこだわる理由も普通の場合ないと思うので、私のようにDockerコンテナのサイズに異常に執着するのでなければ、スタンドアロンを諦めて普通にデプロイしたほうが幸せになれると思います

おまけ: 上手くいかなかった方法

Next.jsのドキュメントにある明示的に含めるファイルを設定する方法でも解決できると思い試しましたが、効果はありませんでした...

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
    /* config options here */
    output: "standalone",
    outputFileTracingIncludes: {
        "/api/\\[\\[\\.\\.\\.slugs\\]\\]": [
            "./node_modules/elysia/dist/**/*",
        ],
        "/auth/\\[\\[\\.\\.\\.slugs\\]\\]": [
            "./node_modules/elysia/dist/**/*",
        ],
    },
};

export default nextConfig;

理由はよく分かりません。今度暇があればElysiaJSのソースコードを読んでAOTが具体的に何をしているか調べてみようと思います。

Discussion