Saltar al contenido
jsonbeautifiers
Español
V8 Verificado ejecutando el parser en Node v24.15.0, V8 13.6.233.17.

Unexpected token '<', "<!DOCTYPE "... is not valid JSON

Tu código llamó a JSON.parse (o a res.json()) sobre un documento HTML. El signo de menor que hay en la posición 0 es la apertura de <!DOCTYPE html>. El JSON no está mal formado; es que no has recibido JSON en absoluto. No hay nada que arreglar en el parser. Hay que arreglar la petición.

También puede que lo veas escrito así

  • Unexpected token < in JSON at position 0
  • Unexpected token '<', "<html><hea"... is not valid JSON
  • SyntaxError: Unexpected token < in JSON at position 0

V8 reescribió la mayoría de sus mensajes de error de JSON en 2022. La redacción antigua sigue siendo la que muestran casi todos los resultados de búsqueda, pero ningún runtime actual la emite.

Pega tu JSON y ve exactamente dónde se rompe

Entrada

Nada de lo que pegues sale de tu navegador. La lista de permitidos de connect-src convierte eso en una garantía del navegador, no en una promesa. Compruébalo tú mismo

Qué lo provoca realmente

Ordenados por la frecuencia con la que cada uno acaba siendo la respuesta.

  1. 01 El servidor devolvió una página de error

    Un 404, un 500 o un error de gateway renderizado como HTML. Muchos frameworks devuelven páginas de error en HTML incluso para rutas de API, salvo que pidas JSON explícitamente. Comprueba primero el código de estado, no el cuerpo.

    Falla

    const data = await fetch('/api/user').then((r) => r.json());

    Funciona

    const res = await fetch('/api/user', { headers: { Accept: 'application/json' } });
    if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
    const type = res.headers.get('content-type') ?? '';
    if (!type.includes('application/json')) {
      throw new Error(`Expected JSON, got ${type}: ${(await res.text()).slice(0, 200)}`);
    }
    const data = await res.json();
  2. 02 Una single-page app reescribió una ruta desconocida a index.html

    Esta es la causa más habitual en desarrollo. Vite, Create React App y casi todos los servidores de desarrollo sirven index.html para cualquier ruta que no reconozcan, así que una errata en una ruta de API o un proxy mal configurado devuelve tu propia aplicación como HTML con un estado 200. Por eso comprobar res.ok no basta por sí solo.

  3. 03 La URL se resolvió en un sitio inesperado

    Una ruta relativa como fetch("api/user") se resuelve contra la ruta actual, así que desde /settings/profile pide /settings/api/user. Usa una barra inicial o una URL absoluta.

    Falla

    fetch('api/user')

    Funciona

    fetch('/api/user')
  4. 04 Un portal cautivo, una VPN o un proxy interceptaron la petición

    El wifi de un hotel, un proxy corporativo o un desafío de bots de Cloudflare devuelven su propia página HTML. Esto aparece como un error que solo sufren algunos usuarios, y solo en algunas redes.

  5. 05 PHP imprimió un warning antes del JSON

    Un aviso o warning volcado antes del cuerpo de la respuesta hace que el documento empiece por <br /> en vez de por {. El JSON sigue ahí, después del HTML. Desactiva display_errors en producción.

El mismo error en otros entornos

El problema de fondo es idéntico; solo cambia la redacción. Si un compañero reporta alguno de estos, está mirando exactamente lo mismo que tú.

Python Expecting value: line 1 column 1 (char 0)
C# (Newtonsoft) Unexpected character encountered while parsing value: <. Path '', line 0, position 0.
C# (System.Text.Json) '<' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0.
Java (Jackson) Unexpected character ('<' (code 60)): expected a valid value

Preguntas frecuentes

¿Por qué cambió el mensaje desde "Unexpected token < in JSON at position 0"?
V8 reescribió sus mensajes de error de JSON en 2022 para incluir un fragmento de la entrada problemática. Chrome 104 en adelante, y Node 18 en adelante, imprimen la nueva redacción. La cadena antigua sigue siendo lo que muestran casi todos los resultados de búsqueda y las respuestas de Stack Overflow, pero ningún runtime actual la emite.
¿Está roto mi JSON?
No. Aquí no hay JSON de por medio. El parser está leyendo un documento HTML. Arregla lo que devuelve la petición y el error desaparece sin tocar tu JSON.
El código de estado es 200, ¿por qué es HTML entonces?
Un servidor de desarrollo que hace fallback a index.html para rutas desconocidas devuelve 200 con un cuerpo HTML. Comprueba la cabecera Content-Type además del estado.

Arréglalo ahora

Pega el payload en la herramienta de arriba, o ve directamente a la creada para este trabajo.

Comprobar un payload