From 4b5d3bd4982ac23052db0c460ac105564c6428cf Mon Sep 17 00:00:00 2001 From: AI Assistant Date: Fri, 13 Mar 2026 19:54:28 +0200 Subject: [PATCH] fix(pdf-compress): bypass middleware body buffering for upload routes Next.js middleware buffers the entire request body (10MB default limit) before the route handler runs. middlewareClientMaxBodySize experimental flag doesn't work reliably with standalone output. Solution: exclude api/compress-pdf from middleware matcher so the body streams directly to the route handler. Auth check moved to a shared helper (auth-check.ts) called at the start of each route. Co-Authored-By: Claude Opus 4.6 --- src/app/api/compress-pdf/auth-check.ts | 25 +++++++++++++++++++++++ src/app/api/compress-pdf/cloud/route.ts | 4 ++++ src/app/api/compress-pdf/extreme/route.ts | 4 ++++ src/middleware.ts | 2 +- 4 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 src/app/api/compress-pdf/auth-check.ts diff --git a/src/app/api/compress-pdf/auth-check.ts b/src/app/api/compress-pdf/auth-check.ts new file mode 100644 index 0000000..7e89796 --- /dev/null +++ b/src/app/api/compress-pdf/auth-check.ts @@ -0,0 +1,25 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getToken } from "next-auth/jwt"; + +/** + * Check auth for routes excluded from middleware (large upload routes). + * Returns null if authenticated, or a 401 NextResponse if not. + */ +export async function requireAuth( + req: NextRequest, +): Promise { + // Skip in development + if (process.env.NODE_ENV === "development") return null; + + const token = await getToken({ + req, + secret: process.env.NEXTAUTH_SECRET, + }); + + if (token) return null; + + return NextResponse.json( + { error: "Authentication required" }, + { status: 401 }, + ); +} diff --git a/src/app/api/compress-pdf/cloud/route.ts b/src/app/api/compress-pdf/cloud/route.ts index ea63877..bb8a9ca 100644 --- a/src/app/api/compress-pdf/cloud/route.ts +++ b/src/app/api/compress-pdf/cloud/route.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server"; import { readFile, unlink } from "fs/promises"; import { join } from "path"; import { parseMultipartUpload } from "../parse-upload"; +import { requireAuth } from "../auth-check"; /** * iLovePDF API integration for PDF compression. @@ -30,6 +31,9 @@ async function cleanup(dir: string) { } export async function POST(req: NextRequest) { + const authError = await requireAuth(req); + if (authError) return authError; + if (!ILOVEPDF_PUBLIC_KEY) { return NextResponse.json( { diff --git a/src/app/api/compress-pdf/extreme/route.ts b/src/app/api/compress-pdf/extreme/route.ts index c71f701..267efb9 100644 --- a/src/app/api/compress-pdf/extreme/route.ts +++ b/src/app/api/compress-pdf/extreme/route.ts @@ -6,6 +6,7 @@ import { promisify } from "util"; import { join } from "path"; import { Readable } from "stream"; import { parseMultipartUpload } from "../parse-upload"; +import { requireAuth } from "../auth-check"; const execFileAsync = promisify(execFile); @@ -58,6 +59,9 @@ function streamFileResponse( } export async function POST(req: NextRequest) { + const authError = await requireAuth(req); + if (authError) return authError; + let tmpDir = ""; try { const upload = await parseMultipartUpload(req); diff --git a/src/middleware.ts b/src/middleware.ts index daef670..6450bfb 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -46,6 +46,6 @@ export const config = { * - /favicon.ico, /robots.txt, /sitemap.xml * - Files with extensions (images, fonts, etc.) */ - "/((?!api/auth|api/notifications/digest|auth/signin|_next|favicon\\.ico|robots\\.txt|sitemap\\.xml|.*\\..*).*)", + "/((?!api/auth|api/notifications/digest|api/compress-pdf|auth/signin|_next|favicon\\.ico|robots\\.txt|sitemap\\.xml|.*\\..*).*)", ], };