fix: cap Prisma pool during next build to stop deploy SSG timeouts

Next build workers were each opening up to DATABASE_POOL_SIZE connections and exhausting MySQL (pool active=0), hanging sitemap generation. Cap build pool to 5, fail connect faster, limit SSG concurrency, and make sitemap dynamic.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-21 21:28:27 +02:00
1 parent 803e8f36c1
commit 77931db775
5 files changed
+53 -3

No files matched your search

+2
View File
@@ -44,6 +44,8 @@ const nextConfig: NextConfig = {
static: 180,
},
optimizePackageImports: ["lucide-react"],
// Keep build-time DB fan-out low (shared MySQL with emulator).
staticGenerationMaxConcurrency: 3,
},
// Add caching headers for static assets
+4
View File
@@ -1,6 +1,10 @@
import type { MetadataRoute } from "next";
import { prisma } from "@/lib/prisma";
// Built at request time — avoids competing with SSG workers for scarce DB
// connections during `next build` (pool timeouts killed deploy on sitemap).
export const dynamic = "force-dynamic";
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const appUrl = process.env.APP_URL ?? "http://localhost:3000";
+19
View File
@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { resolvePrismaConnectionLimit } from "./prisma-pool";
describe("resolvePrismaConnectionLimit", () => {
it("caps the pool during production build phases", () => {
expect(resolvePrismaConnectionLimit(40, "phase-production-build")).toBe(5);
expect(
resolvePrismaConnectionLimit(40, "phase-production-compile"),
).toBe(5);
expect(resolvePrismaConnectionLimit(3, "phase-production-build")).toBe(3);
});
it("keeps the configured pool outside build", () => {
expect(resolvePrismaConnectionLimit(40, undefined)).toBe(40);
expect(resolvePrismaConnectionLimit(40, "phase-development-server")).toBe(
40,
);
});
});
+16
View File
@@ -0,0 +1,16 @@
/**
* Cap pool size during `next build` so N workers × pool don't exhaust MySQL.
* Kept free of Prisma/env imports so unit tests can exercise it in isolation.
*/
export function resolvePrismaConnectionLimit(
poolSize: number,
phase = process.env.NEXT_PHASE,
): number {
if (
phase === "phase-production-build" ||
phase === "phase-production-compile"
) {
return Math.min(poolSize, 5);
}
return poolSize;
}
+12 -3
View File
@@ -1,20 +1,29 @@
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
import { env } from "@/env";
import { PrismaClient } from "@/generated/prisma/client";
import { resolvePrismaConnectionLimit } from "@/lib/prisma-pool";
const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };
function createPrismaClient(): PrismaClient {
const url = new URL(env.DATABASE_URL);
const isBuild =
process.env.NEXT_PHASE === "phase-production-build" ||
process.env.NEXT_PHASE === "phase-production-compile";
const connectionLimit = resolvePrismaConnectionLimit(env.DATABASE_POOL_SIZE);
// Fail fast during SSG so Next can retry/fallback instead of hanging 30s+.
const connectTimeout = isBuild
? Math.min(env.DATABASE_CONNECT_TIMEOUT_MS, 5_000)
: env.DATABASE_CONNECT_TIMEOUT_MS;
const adapter = new PrismaMariaDb({
host: url.hostname,
port: Number(url.port) || 3306,
user: decodeURIComponent(url.username),
password: decodeURIComponent(url.password),
database: url.pathname.replace(/^\//, ""),
connectionLimit: env.DATABASE_POOL_SIZE,
connectTimeout: env.DATABASE_CONNECT_TIMEOUT_MS,
acquireTimeout: env.DATABASE_CONNECT_TIMEOUT_MS,
connectionLimit,
connectTimeout,
acquireTimeout: connectTimeout,
idleTimeout: env.DATABASE_IDLE_TIMEOUT_MS,
});
return new PrismaClient({