vercel/next.js · error · ApiError

Invalid JSON

Error message

Invalid JSON

What it means

Thrown as an ApiError(400) by parseJson() when the request body has Content-Type application/json (or application/ld+json) but the body string is not valid JSON. This becomes a 400 Bad Request response to the client. The body parser special-cases empty bodies (returns {}), so only non-empty malformed JSON triggers this.

Source

Thrown at packages/next/src/server/api-utils/node/parse-body.ts:21

import { parse } from 'next/dist/compiled/content-type'
import isError from '../../../lib/is-error'
import type { SizeLimit } from '../../../types'
import { ApiError } from '../index'

/**
 * Parse `JSON` and handles invalid `JSON` strings
 * @param str `JSON` string
 */
function parseJson(str: string): object {
  if (str.length === 0) {
    // special-case empty json body, as it's a common client-side mistake
    return {}
  }

  try {
    return JSON.parse(str)
  } catch (e) {
    throw new ApiError(400, 'Invalid JSON')
  }
}

/**
 * Parse incoming message like `json` or `urlencoded`
 * @param req request object
 */
export async function parseBody(
  req: IncomingMessage,
  limit: SizeLimit
): Promise<any> {
  let contentType
  try {
    contentType = parse(req.headers['content-type'] || 'text/plain')
  } catch {
    contentType = parse('text/plain')
  }
  const { type, parameters } = contentType

View on GitHub (pinned to 0ae8c72462)

Solutions

  1. On the client, ensure the body is produced via JSON.stringify(obj) and Content-Type is application/json.
  2. In the API route, catch the 400 and return a helpful error message guiding the client.
  3. Validate/normalize the Content-Type; if the client isn't sending JSON, switch to the correct content type.

Example fix

// before (client) - malformed body
fetch('/api/save', { method: 'POST', headers: { 'content-type': 'application/json' }, body: "{name:'bad'" })
// after
fetch('/api/save', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name: 'good' }) })
Defensive patterns

Strategy: try-catch

Validate before calling

function tryParseJson(body: string): object {
  try { return JSON.parse(body) }
  catch { throw new Error('Request body is not valid JSON') }
}

Try / catch

// In the API route handler
try {
  // body is already parsed by Next.js; this pattern applies if you parse manually
} catch (err) {
  if (err.message === 'Invalid JSON') res.status(400).json({ error: 'Malformed JSON body' })
}

Prevention

When it happens

Trigger: A POST/PUT/PATCH to an API route with Content-Type: application/json where the raw body fails JSON.parse — e.g. trailing comma, unquoted keys, truncated payload, or text that isn't JSON at all.

Common situations: Client sends malformed JSON (typo, truncated by a proxy, wrong content-type header on a form payload); a fetch call with a stringified-but-broken body; curl sending raw text with a JSON content type.

Understand the failure class

Related errors


AI-assisted analysis of vercel/next.js@0ae8c72462 (2026-08-06). Data as JSON: /api/errors/9470f77f7dc5be34. Report an issue: GitHub.