diff --git a/next.config.js b/next.config.js index 9ddc371..fb1ecbd 100644 --- a/next.config.js +++ b/next.config.js @@ -1,8 +1,16 @@ /** * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful * for Docker builds. + * + * Note: We use dynamic import here to avoid blocking Next.js initialization. + * The env validation will happen when the module is actually used, not during config load. */ -await import("./src/env.js"); +if (!process.env.SKIP_ENV_VALIDATION) { + // Use dynamic import to avoid blocking initialization + import("./src/env.js").catch((err) => { + console.error("Failed to load env validation:", err); + }); +} /** @type {import("next").NextConfig} */ const config = { diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 268ef64..827fd05 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -5,6 +5,14 @@ generator client { datasource db { provider = "postgresql" url = env("DATABASE_URL") + // For Supabase + Vercel serverless optimization: + // - DATABASE_URL: Use Supabase's connection pooling URL (Transaction mode) + // Example: postgresql://user:pass@host:6543/db?pgbouncer=true + // - DIRECT_URL: Use Supabase's direct connection URL (for migrations) + // Example: postgresql://user:pass@host:5432/db + // For local dev without Supabase, set DIRECT_URL to DATABASE_URL + // This separation improves performance and prevents connection pool exhaustion + directUrl = env("DIRECT_URL") } model User { diff --git a/src/env.js b/src/env.js index 77893ad..e914d13 100644 --- a/src/env.js +++ b/src/env.js @@ -8,6 +8,7 @@ export const env = createEnv({ */ server: { DATABASE_URL: z.string().url(), + DIRECT_URL: z.string().url().optional(), NODE_ENV: z .enum(["development", "test", "production"]) .default("development"), @@ -45,6 +46,7 @@ export const env = createEnv({ */ runtimeEnv: { DATABASE_URL: process.env.DATABASE_URL, + DIRECT_URL: process.env.DIRECT_URL, NODE_ENV: process.env.NODE_ENV, // NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET, // NEXTAUTH_URL: process.env.NEXTAUTH_URL, diff --git a/src/server/db.ts b/src/server/db.ts index 9255447..b84d9dc 100644 --- a/src/server/db.ts +++ b/src/server/db.ts @@ -2,16 +2,49 @@ import { PrismaClient } from "@prisma/client"; import { env } from "@/env"; -const createPrismaClient = () => - new PrismaClient({ +const createPrismaClient = () => { + const client = new PrismaClient({ log: env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"], }); + // In serverless environments (Vercel), we want to avoid eager connection + // Prisma will connect lazily on first query, which is better for cold starts + // Don't call $connect() here - let Prisma handle connections on-demand + + return client; +}; + const globalForPrisma = globalThis as unknown as { prisma: ReturnType | undefined; }; -export const db = globalForPrisma.prisma ?? createPrismaClient(); +// Reuse Prisma client across invocations to optimize connection pooling +// In Vercel serverless, the same container may handle multiple requests +// Reusing the client prevents creating new connections for each request +export const db = + globalForPrisma.prisma ?? createPrismaClient(); -if (env.NODE_ENV !== "production") globalForPrisma.prisma = db; +// Store in globalThis for reuse across all environments +// This is especially important in serverless where the same container +// may handle multiple requests, allowing connection reuse +if (!globalForPrisma.prisma) { + globalForPrisma.prisma = db; +} + +// Graceful shutdown handling +if (typeof process !== "undefined") { + process.on("beforeExit", async () => { + await db.$disconnect(); + }); + + process.on("SIGINT", async () => { + await db.$disconnect(); + process.exit(0); + }); + + process.on("SIGTERM", async () => { + await db.$disconnect(); + process.exit(0); + }); +}