Skip to content

Instantly share code, notes, and snippets.

@alexsorokoletov
Created January 13, 2026 16:34
Show Gist options
  • Select an option

  • Save alexsorokoletov/fec83b77a64027c618941d7a52759463 to your computer and use it in GitHub Desktop.

Select an option

Save alexsorokoletov/fec83b77a64027c618941d7a52759463 to your computer and use it in GitHub Desktop.
Next.js 15 API Routes: Production Build Gotchas & Fixes

Next.js 15 API Routes: Production Build Gotchas

The Problem

When running next build (production) vs next dev, API route handlers can silently fail with:

Error: No response is returned from route handler '/path/to/route.ts'. 
Ensure you return a `Response` or a `NextResponse` in all branches of your handler.

Even when your code clearly returns a Response, and even when console.log confirms the handler executed successfully.

Root Cause

Next.js 15.5.x has bugs with static Response methods in production builds. The following DO NOT work reliably:

// ❌ BROKEN in production
return Response.json({ data: 'value' });
return Response.json({ error: 'msg' }, { status: 400 });
return NextResponse.json({ data: 'value' });
return Response.redirect('https://example.com');

The Fix

Use the Response constructor directly:

// ✅ WORKS in production
return new Response(JSON.stringify({ data: 'value' }), {
  status: 200,
  headers: { 'Content-Type': 'application/json' },
});

// ✅ For redirects
return new Response(null, {
  status: 302,
  headers: { Location: 'https://example.com' },
});

Quick Reference

Broken Fixed
Response.json(data) new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' } })
Response.json(data, { status: 400 }) new Response(JSON.stringify(data), { status: 400, headers: { 'Content-Type': 'application/json' } })
NextResponse.json(data) Same as above
Response.redirect(url) new Response(null, { status: 302, headers: { Location: url } })

Prisma on Vercel

Problem

PrismaClientInitializationError: Prisma Client could not locate the Query Engine 
for runtime "rhel-openssl-3.0.x"

Fix

Add binary targets to prisma/schema.prisma:

generator client {
  provider      = "prisma-client-js"
  binaryTargets = ["native", "rhel-openssl-3.0.x"]
}

And remove @prisma/client from optimizePackageImports in next.config.ts (it conflicts with engine bundling).

Page Export Validation

Next.js pages can only export specific things. Invalid exports cause routes to silently disappear from the build.

// ❌ BAD - causes route to be skipped
export default function Page() { ... }
export function someUtilityFunction() { ... }  // Invalid!

// ✅ GOOD - move utilities to separate file
// page.tsx
export default function Page() { ... }

// lib/utils.ts
export function someUtilityFunction() { ... }

OAuth/MCP Client Authentication

When implementing OAuth for MCP (Model Context Protocol) clients like Claude Desktop:

Problem

Claude Desktop may register with token_endpoint_auth_method: 'client_secret_post' but actually operates as a public client (no secret, uses PKCE).

Fix

Treat clients without a stored clientSecret as public clients, regardless of tokenEndpointAuthMethod:

const isPublicClient = 
  client.tokenEndpointAuthMethod === 'none' || 
  !client.clientSecret;  // ← Key addition

if (isPublicClient) {
  // PKCE validation only, no client_secret required
} else {
  // Validate client_secret
}

Debugging Tips

  1. Dev works, prod doesn't? → Check for Response.json() / Response.redirect()
  2. Routes missing from build? → Check page exports, look at app-paths-manifest.json
  3. Prisma engine not found? → Add binaryTargets to schema
  4. OAuth 401 on token exchange? → Log tokenEndpointAuthMethod and clientSecret presence
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment