Remix + Cloudflare + Prisma で、Node.jsとWrangler実行時にimportを適切に切り替える
Remix + Cloudflare で Prisma を使う場合の問題点
Prisma を使う場合に必要になるパッケージ
remix vite:dev (Node.js) | wrangler pages dev (Cloudflare Edge) |
---|---|
pg | @prisma/adapter-pg-worker |
@prisma/adapter-pg | @prisma/pg-worker |
開発時の実行方法によってランタイム環境が異なるため、インポートするパッケージを変えなければなりません。
実行環境によるインポートファイルの切り替え
https://github.com/SoraKumo001/cloudflare-remix-env-paths
インポートの切り替えは Vite の機能を使えば簡単に行えます。
vite でエイリアスを仕込む
vite.config.ts
NODE_ENV に応じて、インポート時のコードが切り替わるようにします。
1import {2 vitePlugin as remix,3 cloudflareDevProxyVitePlugin as remixCloudflareDevProxy,4} from "@remix-run/dev";5import { defineConfig } from "vite";6import tsconfigPaths from "vite-tsconfig-paths";7import path from "path";89export default defineConfig({10 resolve: {11 alias:12 process.env.NODE_ENV === "development"13 ? {14 "~/prisma": path.resolve(__dirname, "./app/prisma/index.dev"),15 }16 : undefined,17 },18 plugins: [19 remixCloudflareDevProxy(),20 remix({21 future: {22 v3_fetcherPersist: true,23 v3_relativeSplatPath: true,24 v3_throwAbortReason: true,25 },26 }),27 tsconfigPaths(),28 ],29});
必要なパッケージを切り出す
Node.js と Wrangler で、呼び出すパッケージの違いを吸収するため、それぞれのインポートを行います。このサンプルは必要なものを export しているだけですが、ここで Prisma クライアントを生成する関数を作っても問題ありません。
app/prisma/index.ts
1export * from "@prisma/adapter-pg-worker";2import pg from "@prisma/pg-worker";3export const Pool = pg.Pool;
app/prisma/index.dev.ts
1export * from "@prisma/adapter-pg";2import pg from "pg";3export const Pool = pg.Pool;
paths で設定したエイリアスでインポートする
Pool と PrismaPg は、Vite のエイリアスによって環境に応じて自動的に切り替わります。
app/routes/_index.tsx
1import { LoaderFunctionArgs } from "@remix-run/cloudflare";2import { Pool, PrismaPg } from "~/prisma";3import { PrismaClient } from "@prisma/client";4import { useLoaderData } from "@remix-run/react";56export default function Index() {7 const values = useLoaderData<string[]>();8 return (9 <div>10 {values.map((v) => (11 <div key={v}>{v}</div>12 ))}13 </div>14 );15}1617export async function loader({18 context,19}: LoaderFunctionArgs): Promise<string[]> {20 const url = new URL(context.cloudflare.env.DATABASE_URL);21 const schema = url.searchParams.get("schema") ?? undefined;22 const pool = new Pool({23 connectionString: context.cloudflare.env.DATABASE_URL,24 });25 const adapter = new PrismaPg(pool, { schema });26 const prisma = new PrismaClient({ adapter });27 await prisma.test.create({ data: {} });28 return prisma.test.findMany().then((r) => r.map(({ id }) => id));29}
なお、あまり言及されることがありませんが接続文字列にschemaを設定している場合、PrismaPgにパラメータを送らないと認識されないので注意してください。
まとめ
環境による差異は、Vite の機能で吸収可能です。ただ、Cloudflare を無料プランで動作させるのを前提とした場合は Remix + Prisma をそのまま使う組み合わせはおすすめできません。サンプルレベルの内容なら問題ないのですが、ある程度まともに作っていくと、無料プランで使用可能な容量をオーバーしてしまうからです。そうなるとフロントとバックエンドを分離するか、巨大な Prisma エンジンを分離するかという選択になります。
フロントとバックエンドの分離は Cloudflare Pages + Cloudflare Workers のような形にする必要があります。また、Prisma エンジンの分離は Prisma Accelarate を使うか、この機能をセルフホストするかという選択になります。
私の場合は全て無料にするためにセルフホストという選択を取っています。Prisma Accelarate のセルフホストから画像最適化まで、全部自前で作りました。足りないものは作れば全て解決です。
https://next-blog.croud.jp/contents/eec22faf-3563-4a77-9a47-4dfddc604141
また、画像最適化に関しては Deno Deploy で Edge Cache の機能が使えるようになったため、こちらで使うのを前提とする avif 対応のパッケージを作りました。avif のエンコードはサイズが大きく、パッケージが圧縮時で 1.5MB になるため、Cloudflare の無料プランで動かず、以前作ったパッケージではあえて機能を入れていませんでした。しかしサイズ制限のない Deno Deploy では問題なく動きます。
https://www.npmjs.com/package/wasm-image-optimization-avif
Deno Deploy 用の画像最適化の記事はこちらです。
https://next-blog.croud.jp/contents/b9a80cee-4803-4fd1-912a-2610c2aa4d70