search
JavaScript star Featured

Fix: Unexpected token < in JSON at position 0 error - Complete Guide

Complete guide to fix 'Unexpected token < in JSON at position 0' error. Learn how to resolve JSON parsing issues when HTML is returned instead of JSON with practical solutions for React, Angular, and Vue applications.

person By Gautam Sharma
calendar_today January 8, 2026
schedule 22 min read
JSON Error Parsing Frontend React Angular Vue API Debugging JavaScript

The ‘Unexpected token < in JSON at position 0’ error is a common issue developers encounter when attempting to parse JSON data, but the server returns HTML content (like an error page) instead of the expected JSON. This error typically occurs during API calls when the server responds with an HTML error page rather than JSON data, causing the JSON parser to fail when it encounters the opening < character of HTML tags.

This comprehensive guide explains what causes this error, why it happens, and provides multiple solutions to fix it in your React, Angular, and Vue projects with clean code examples and directory structure.


What is the ‘Unexpected token < in JSON at position 0’ Error?

The “Unexpected token < in JSON at position 0” error occurs when:

  • Your application expects JSON data from an API call
  • The server returns HTML content (usually an error page) instead of JSON
  • The JSON parser tries to parse HTML, starting with < character
  • The parser throws an error because < is not valid JSON syntax
  • API calls fail with parsing errors instead of proper error handling

Common Error Manifestations:

  • SyntaxError: Unexpected token < in JSON at position 0
  • API calls returning HTML error pages instead of JSON
  • JSON.parse() failing on server responses
  • Frontend expecting JSON but receiving HTML
  • Server-side routing issues returning index.html instead of API responses

Understanding the Problem

This error typically occurs due to:

  • Server-side routing misconfiguration
  • API endpoints returning HTML error pages
  • Backend server not properly handling API routes
  • Frontend making requests to wrong endpoints
  • Server returning 404 or 500 HTML pages instead of JSON errors
  • Proxy configuration issues
  • CORS and cross-origin request problems

Why This Error Happens:

When you make an API call expecting JSON data, but the server returns an HTML page (like a 404 error page or server error page), the JSON parser receives HTML starting with <html>, <head>, or <body> tags. Since < is not valid JSON syntax, the parser throws the “Unexpected token < in JSON at position 0” error.


Solution 1: Check Server Response Before Parsing

The first step is to verify the response type before attempting to parse JSON.

❌ Without Response Type Checking:

// ❌ Don't parse JSON without checking response
fetch('/api/data')
  .then(response => response.json()) // ❌ This will fail if response is HTML
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

✅ With Response Type Checking:

JavaScript Response Validation:

// ✅ Check response type before parsing JSON
async function fetchJsonData(url) {
  try {
    const response = await fetch(url);
    
    // ✅ Check if response is OK and has JSON content type
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const contentType = response.headers.get('content-type');
    if (!contentType || !contentType.includes('application/json')) {
      // ✅ Handle non-JSON responses appropriately
      const text = await response.text();
      console.warn('Non-JSON response received:', text.substring(0, 100) + '...');
      throw new Error('Response is not JSON');
    }
    
    const jsonData = await response.json();
    return jsonData;
  } catch (error) {
    console.error('Fetch error:', error);
    throw error;
  }
}

// ✅ Usage
fetchJsonData('/api/data')
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

Solution 2: React - Fix JSON Parsing Issues

React API Call Issues

❌ With Improper Error Handling:

// src/services/api.js - ❌ Not checking response type
import axios from 'axios';

export const fetchData = async () => {
  const response = await axios.get('/api/data');
  // ❌ This will fail if server returns HTML
  return response.data;
};

✅ With Proper Response Validation:

API Service with Response Validation:
// src/services/api.js
import axios from 'axios';

// ✅ Axios interceptor to validate JSON responses
axios.interceptors.response.use(
  response => {
    // ✅ Check if response contains JSON data
    const contentType = response.headers['content-type'];
    if (contentType && contentType.includes('application/json')) {
      return response;
    } else if (response.data && typeof response.data === 'string' && response.data.trim().startsWith('<')) {
      // ✅ Handle HTML responses
      console.warn('Received HTML instead of JSON:', response.data.substring(0, 100));
      return Promise.reject(new Error('Server returned HTML instead of JSON'));
    }
    return response;
  },
  error => {
    // ✅ Handle error responses
    if (error.response) {
      if (error.response.data && typeof error.response.data === 'string' && error.response.data.trim().startsWith('<')) {
        console.error('HTML error page received:', error.response.data.substring(0, 100));
        return Promise.reject(new Error('Server error page received instead of JSON'));
      }
    }
    return Promise.reject(error);
  }
);

export const fetchData = async () => {
  try {
    const response = await axios.get('/api/data');
    return response.data;
  } catch (error) {
    console.error('API call failed:', error);
    throw error;
  }
};

React Component with Safe JSON Parsing

❌ Unsafe JSON Parsing:

// src/components/DataComponent.js - ❌ Unsafe JSON handling
import React, { useState, useEffect } from 'react';

const DataComponent = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch('/api/data')
      .then(res => res.json()) // ❌ Will fail if HTML is returned
      .then(setData)
      .catch(console.error);
  }, []);

  return <div>{data && <pre>{JSON.stringify(data, null, 2)}</pre>}</div>;
};

✅ Safe JSON Parsing:

// src/components/DataComponent.js
import React, { useState, useEffect } from 'react';

const DataComponent = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        setError(null);
        
        const response = await fetch('/api/data');
        
        // ✅ Check response status and content type
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const contentType = response.headers.get('content-type');
        if (!contentType || !contentType.includes('application/json')) {
          // ✅ Handle HTML response
          const text = await response.text();
          if (text.trim().startsWith('<')) {
            throw new Error('Server returned HTML instead of JSON');
          }
        }
        
        const jsonData = await response.json();
        setData(jsonData);
      } catch (err) {
        setError(err.message);
        console.error('Fetch error:', err);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, []);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return <div>{data && <pre>{JSON.stringify(data, null, 2)}</pre>}</div>;
};

Solution 3: Angular - Fix JSON Parsing Issues

Angular HTTP Client Configuration

❌ With Improper Error Handling:

// src/app/services/data.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  constructor(private http: HttpClient) { }

  getData(): Observable<any> {
    // ❌ No validation of response type
    return this.http.get('/api/data');
  }
}

✅ With Response Validation:

Service with Response Validation:
// src/app/services/data.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpResponse, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  constructor(private http: HttpClient) { }

  getData(): Observable<any> {
    return this.http.get('/api/data', { observe: 'response', responseType: 'text' })
      .pipe(
        map(response => {
          // ✅ Check content type and validate response
          const contentType = response.headers.get('content-type');
          
          if (!contentType || !contentType.includes('application/json')) {
            // ✅ Check if response looks like HTML
            const body = response.body;
            if (body && body.trim().startsWith('<')) {
              throw new Error('Server returned HTML instead of JSON');
            }
          }
          
          // ✅ Parse JSON safely
          try {
            return JSON.parse(response.body);
          } catch (parseError) {
            throw new Error('Failed to parse JSON response');
          }
        }),
        catchError((error: HttpErrorResponse) => {
          if (error.status !== 0) {
            // ✅ Check if error response is HTML
            if (error.error && typeof error.error === 'string' && error.error.trim().startsWith('<')) {
              console.error('HTML error page received:', error.error.substring(0, 100));
              return throwError(() => new Error('Server error page received instead of JSON'));
            }
          }
          return throwError(() => error);
        })
      );
  }
}

Angular HTTP Interceptor

src/app/interceptors/json-validation.interceptor.ts:

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { map, catchError } from 'rxjs/operators';

@Injectable()
export class JsonValidationInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(req).pipe(
      map(event => {
        if (event instanceof HttpResponse) {
          // ✅ Check if response is JSON
          const contentType = event.headers.get('content-type');
          
          if (event.body && typeof event.body === 'string' && 
              (!contentType || !contentType.includes('application/json')) &&
              event.body.trim().startsWith('<')) {
            // ✅ Handle HTML response
            console.error('HTML response received instead of JSON:', event.body.substring(0, 100));
            throw new Error('Server returned HTML instead of JSON');
          }
        }
        return event;
      }),
      catchError(error => {
        if (error instanceof HttpErrorResponse) {
          // ✅ Handle HTML error responses
          if (error.error && typeof error.error === 'string' && error.error.trim().startsWith('<')) {
            console.error('HTML error page received:', error.error.substring(0, 100));
            return throwError(() => new Error('Server error page received instead of JSON'));
          }
        }
        return throwError(() => error);
      })
    );
  }
}

Solution 4: Vue - Fix JSON Parsing Issues

Vue API Call Issues

❌ With Improper Error Handling:

// src/api/index.js - ❌ Not validating response type
import axios from 'axios';

const api = axios.create({
  baseURL: process.env.VUE_APP_API_URL || '/api'
});

export const fetchData = async () => {
  const response = await api.get('/data');
  // ❌ This will fail if server returns HTML
  return response.data;
};

✅ With Response Validation:

API Service with Validation:
// src/api/index.js
import axios from 'axios';

const api = axios.create({
  baseURL: process.env.VUE_APP_API_URL || '/api'
});

// ✅ Response interceptor to validate JSON
api.interceptors.response.use(
  response => {
    // ✅ Check if response is JSON
    const contentType = response.headers['content-type'];
    
    if (response.data && typeof response.data === 'string' && 
        (!contentType || !contentType.includes('application/json')) &&
        response.data.trim().startsWith('<')) {
      // ✅ Handle HTML response
      console.error('HTML response received instead of JSON:', response.data.substring(0, 100));
      return Promise.reject(new Error('Server returned HTML instead of JSON'));
    }
    
    return response;
  },
  error => {
    // ✅ Handle error responses that might be HTML
    if (error.response && error.response.data && 
        typeof error.response.data === 'string' && 
        error.response.data.trim().startsWith('<')) {
      console.error('HTML error page received:', error.response.data.substring(0, 100));
      return Promise.reject(new Error('Server error page received instead of JSON'));
    }
    return Promise.reject(error);
  }
);

export const fetchData = async () => {
  try {
    const response = await api.get('/data');
    return response.data;
  } catch (error) {
    console.error('API call failed:', error);
    throw error;
  }
};

Vue Component with Safe JSON Handling

❌ Unsafe JSON Parsing:

<!-- src/components/DataComponent.vue - ❌ Unsafe JSON handling -->
<template>
  <div>
    <div v-if="loading">Loading...</div>
    <div v-else-if="error">Error: {{ error }}</div>
    <div v-else>{{ data }}</div>
  </div>
</template>

<script>
import { ref, onMounted } from 'vue'
import { fetchData } from '@/api'

export default {
  name: 'DataComponent',
  setup() {
    const data = ref(null)
    const loading = ref(true)
    const error = ref(null)

    onMounted(async () => {
      try {
        // ❌ Will fail if server returns HTML
        data.value = await fetchData()
      } catch (err) {
        error.value = err.message
      } finally {
        loading.value = false
      }
    })

    return { data, loading, error }
  }
}
</script>

✅ Safe JSON Parsing:

<!-- src/components/DataComponent.vue -->
<template>
  <div>
    <div v-if="loading">Loading...</div>
    <div v-else-if="error">Error: {{ error }}</div>
    <div v-else-if="data">{{ data }}</div>
  </div>
</template>

<script>
import { ref, onMounted } from 'vue'

export default {
  name: 'DataComponent',
  setup() {
    const data = ref(null)
    const loading = ref(true)
    const error = ref(null)

    const fetchData = async () => {
      try {
        const response = await fetch('/api/data')
        
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`)
        }
        
        const contentType = response.headers.get('content-type')
        if (!contentType || !contentType.includes('application/json')) {
          // ✅ Check if response is HTML
          const text = await response.text()
          if (text.trim().startsWith('<')) {
            throw new Error('Server returned HTML instead of JSON')
          }
          // ✅ If it's not JSON but not HTML, try to parse anyway
          return JSON.parse(text)
        }
        
        return await response.json()
      } catch (err) {
        console.error('Fetch error:', err)
        throw err
      }
    }

    onMounted(async () => {
      try {
        data.value = await fetchData()
      } catch (err) {
        error.value = err.message
      } finally {
        loading.value = false
      }
    })

    return { data, loading, error }
  }
}
</script>

Solution 5: Server-Side Configuration Fixes

Express.js API Route Configuration

❌ Without Proper API Route Handling:

// server.js - ❌ API routes not properly configured
const express = require('express');
const app = express();

app.use(express.static('public'));

// ❌ This route might return HTML instead of JSON
app.get('/api/data', (req, res) => {
  // If this fails, it might return an HTML error page
  res.json({ data: 'some data' });
});

// ❌ Catch-all route interfering with API routes
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'index.html')); // ❌ This catches API routes too
});

✅ With Proper API Route Handling:

Express.js Configuration:
// server.js
const express = require('express');
const path = require('path');
const app = express();

// ✅ Parse JSON bodies
app.use(express.json());

// ✅ API routes FIRST, before static file serving
app.get('/api/data', (req, res) => {
  try {
    // ✅ Always return proper JSON responses
    res.json({ 
      success: true,
      data: 'some data',
      timestamp: new Date().toISOString()
    });
  } catch (error) {
    // ✅ Return proper JSON error responses
    res.status(500).json({
      success: false,
      error: 'Internal server error',
      message: error.message
    });
  }
});

// ✅ Other API routes...

// ✅ Static file serving for frontend
app.use(express.static('public'));

// ✅ Catch-all route for frontend routes (NOT API routes)
app.get(/^(?!\/api\/)/, (req, res) => {
  // ✅ Only serve index.html for non-API routes
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Node.js Error Handling

❌ Without Proper Error Handling:

// api/handlers.js - ❌ Generic error handling
const handleRequest = async (req, res) => {
  try {
    const data = await getDataFromDatabase();
    res.send(data); // ❌ Might send HTML if error occurs
  } catch (error) {
    res.status(500).send('Error occurred'); // ❌ Sending HTML error
  }
};

✅ With Proper JSON Error Handling:

// api/handlers.js
const handleRequest = async (req, res) => {
  try {
    const data = await getDataFromDatabase();
    // ✅ Explicitly set content type and send JSON
    res.setHeader('Content-Type', 'application/json');
    res.status(200).json({
      success: true,
      data: data
    });
  } catch (error) {
    // ✅ Always return JSON error responses for API routes
    console.error('API Error:', error);
    res.status(500).json({
      success: false,
      error: 'Internal server error',
      message: process.env.NODE_ENV === 'production' ? 'An error occurred' : error.message
    });
  }
};

Solution 6: Proxy Configuration Fixes

Webpack Dev Server Proxy

❌ Without Proper Proxy Configuration:

// webpack.config.js - ❌ Improper proxy setup
module.exports = {
  devServer: {
    proxy: {
      '/api': 'http://localhost:3001' // ❌ Might not preserve content types
    }
  }
};

✅ With Proper Proxy Configuration:

Create React App (package.json):
{
  "name": "my-app",
  "proxy": "http://localhost:3001",
  "private": true,
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}
Or with setupProxy.js:
// src/setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'http://localhost:3001',
      changeOrigin: true,
      // ✅ Ensure proper headers are preserved
      onProxyReq: (proxyReq, req, res) => {
        // ✅ Set proper content type for API requests
        if (req.path.startsWith('/api')) {
          proxyReq.setHeader('Accept', 'application/json');
        }
      },
      onProxyRes: (proxyRes, req, res) => {
        // ✅ Ensure API responses have correct content type
        if (req.path.startsWith('/api')) {
          proxyRes.headers['content-type'] = 'application/json';
        }
      }
    })
  );
};

Solution 7: Advanced Error Handling Patterns

Generic JSON Parser with Validation

utils/safeJsonParser.js:

/**
 * Safely parse JSON with validation
 * @param {string} jsonString - String to parse as JSON
 * @param {Object} options - Options for parsing
 * @returns {Object} Parsed JSON object
 */
export function safeJsonParse(jsonString, options = {}) {
  const { 
    allowEmpty = false, 
    validateStructure = false,
    onError = null 
  } = options;

  try {
    // ✅ Check if string looks like HTML
    if (typeof jsonString === 'string' && jsonString.trim().startsWith('<')) {
      throw new Error('String appears to be HTML, not JSON');
    }

    // ✅ Check for empty strings
    if (!jsonString && !allowEmpty) {
      throw new Error('Empty string provided for JSON parsing');
    }

    // ✅ Parse JSON
    const parsed = JSON.parse(jsonString);

    // ✅ Optional structure validation
    if (validateStructure && typeof parsed !== 'object') {
      throw new Error('Parsed JSON is not an object or array');
    }

    return parsed;
  } catch (error) {
    if (onError) {
      onError(error);
    }
    throw error;
  }
}

/**
 * Fetch and safely parse JSON response
 * @param {string} url - URL to fetch
 * @param {Object} options - Fetch options
 * @returns {Promise<Object>} Parsed JSON response
 */
export async function fetchAndParseJson(url, options = {}) {
  try {
    const response = await fetch(url, {
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
        ...options.headers
      },
      ...options
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const contentType = response.headers.get('content-type');
    if (!contentType || !contentType.includes('application/json')) {
      const text = await response.text();
      if (text.trim().startsWith('<')) {
        throw new Error('Server returned HTML instead of JSON');
      }
      // ✅ Try to parse the text as JSON anyway
      return safeJsonParse(text);
    }

    const jsonText = await response.text();
    return safeJsonParse(jsonText);
  } catch (error) {
    console.error('Fetch and parse error:', error);
    throw error;
  }
}

Usage Example:

// components/SafeDataComponent.js
import React, { useState, useEffect } from 'react';
import { fetchAndParseJson } from '../utils/safeJsonParser';

const SafeDataComponent = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const result = await fetchAndParseJson('/api/data');
        setData(result);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, []);

  if (loading) return <div>Loading safely...</div>;
  if (error) return <div>Error: {error}</div>;

  return <div>{data && <pre>{JSON.stringify(data, null, 2)}</pre>}</div>;
};

Working Code Examples

Complete React Solution:

// src/hooks/useApi.js
import { useState, useEffect } from 'react';

export const useApi = (url, options = {}) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let isCancelled = false;

    const fetchData = async () => {
      try {
        setLoading(true);
        setError(null);

        const response = await fetch(url, {
          headers: {
            'Accept': 'application/json',
            ...options.headers
          },
          ...options
        });

        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }

        const contentType = response.headers.get('content-type');
        if (!contentType || !contentType.includes('application/json')) {
          const text = await response.text();
          if (text.trim().startsWith('<')) {
            throw new Error('Server returned HTML instead of JSON');
          }
          // Attempt to parse as JSON
          try {
            const jsonData = JSON.parse(text);
            if (!isCancelled) setData(jsonData);
          } catch (parseError) {
            throw new Error('Response is not valid JSON');
          }
        } else {
          const jsonData = await response.json();
          if (!isCancelled) setData(jsonData);
        }
      } catch (err) {
        if (!isCancelled) {
          setError(err.message);
          console.error('API hook error:', err);
        }
      } finally {
        if (!isCancelled) setLoading(false);
      }
    };

    if (url) {
      fetchData();
    }

    return () => {
      isCancelled = true;
    };
  }, [url]);

  return { data, loading, error };
};

// ✅ Usage in component
const MyComponent = () => {
  const { data, loading, error } = useApi('/api/data');

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return <div>{data && <pre>{JSON.stringify(data, null, 2)}</pre>}</div>;
};

Complete Angular Solution:

// src/app/services/safe-http.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class SafeHttpService {
  constructor(private http: HttpClient) { }

  get<T>(url: string): Observable<T> {
    return this.http.get(url, { observe: 'response', responseType: 'text' }).pipe(
      map(response => {
        // ✅ Validate response is JSON
        const contentType = response.headers.get('content-type');
        
        if (!contentType || !contentType.includes('application/json')) {
          if (response.body && typeof response.body === 'string' && response.body.trim().startsWith('<')) {
            throw new Error('Server returned HTML instead of JSON');
          }
        }

        try {
          return JSON.parse(response.body) as T;
        } catch (parseError) {
          throw new Error('Failed to parse JSON response');
        }
      }),
      catchError((error: HttpErrorResponse) => {
        if (error.error && typeof error.error === 'string' && error.error.trim().startsWith('<')) {
          console.error('HTML error page received:', error.error.substring(0, 100));
          return throwError(() => new Error('Server error page received instead of JSON'));
        }
        return throwError(() => error);
      })
    );
  }
}

Complete Vue Solution:

// src/composables/useApi.js
import { ref } from 'vue';

export function useApi() {
  const data = ref(null);
  const loading = ref(false);
  const error = ref(null);

  const get = async (url) => {
    try {
      loading.value = true;
      error.value = null;

      const response = await fetch(url, {
        headers: {
          'Accept': 'application/json'
        }
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const contentType = response.headers.get('content-type');
      if (!contentType || !contentType.includes('application/json')) {
        const text = await response.text();
        if (text.trim().startsWith('<')) {
          throw new Error('Server returned HTML instead of JSON');
        }
        data.value = JSON.parse(text);
      } else {
        data.value = await response.json();
      }
    } catch (err) {
      error.value = err.message;
      console.error('API error:', err);
    } finally {
      loading.value = false;
    }
  };

  return {
    data,
    loading,
    error,
    get
  };
}

// ✅ Usage in component
import { useApi } from '@/composables/useApi';

export default {
  name: 'MyComponent',
  setup() {
    const { data, loading, error, get: fetchData } = useApi();

    onMounted(() => {
      fetchData('/api/data');
    });

    return {
      data,
      loading,
      error
    };
  }
};

Best Practices for JSON Handling

1. Always Validate Response Type

// ✅ Always check content type before parsing
const response = await fetch('/api/data');
const contentType = response.headers.get('content-type');

if (contentType && contentType.includes('application/json')) {
  const data = await response.json();
} else {
  // Handle non-JSON response
}

2. Use Proper Error Handling

// ✅ Implement comprehensive error handling
try {
  const response = await fetch('/api/data');
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  
  const contentType = response.headers.get('content-type');
  if (!contentType.includes('application/json')) {
    const text = await response.text();
    if (text.startsWith('<')) throw new Error('HTML response received');
  }
  
  const data = await response.json();
} catch (error) {
  console.error('API call failed:', error);
}

3. Implement Response Interceptors

// ✅ Use interceptors to handle responses globally
// See examples above for React/Angular/Vue interceptors

4. Test Error Scenarios

// ✅ Test with mock HTML responses to ensure proper handling
// Implement unit tests for error cases

Debugging Steps

Step 1: Inspect Network Tab

# Open browser dev tools
# Go to Network tab
# Find the failing request
# Check Response tab to see actual content
# Verify if it's HTML instead of JSON

Step 2: Check Server Logs

# Check server logs for error messages
# Look for 404, 500, or other error responses
# Verify API routes are properly configured

Step 3: Test API Endpoint Directly

# Test API endpoint directly with curl or Postman
curl -H "Accept: application/json" https://your-api.com/api/data

Step 4: Verify Content-Type Headers

# Check that server sends correct Content-Type header
# Application should return 'application/json' for API endpoints

Common Mistakes to Avoid

1. Not Checking Response Type

// ❌ Don't assume all responses are JSON
fetch('/api/data').then(r => r.json()); // ❌ Will fail if HTML returned

// ✅ Always check response type
fetch('/api/data')
  .then(r => {
    if (r.headers.get('content-type')?.includes('application/json')) {
      return r.json();
    }
    throw new Error('Not JSON response');
  });

2. Forgetting Server-Side Route Configuration

// ❌ Don't let catch-all routes interfere with API routes
app.get('*', (req, res) => {
  res.sendFile('index.html'); // ❌ This catches API routes too
});

// ✅ Handle API routes separately
app.get('/api/*', apiHandler); // ✅ API routes first
app.get('*', frontendHandler); // ✅ Then catch-all for frontend

3. Improper Error Handling

// ❌ Don't return HTML for API errors
res.status(500).send('Error occurred'); // ❌ Sends HTML

// ✅ Always return JSON for API errors
res.status(500).json({ error: 'Internal server error' }); // ✅ Sends JSON

4. Not Using Accept Headers

// ❌ Don't forget to specify expected response type
fetch('/api/data'); // ❌ Doesn't specify accept header

// ✅ Specify that you expect JSON
fetch('/api/data', {
  headers: { 'Accept': 'application/json' }
});

Performance Considerations

1. Efficient Response Validation

// ✅ Validate responses efficiently
const isValidJsonResponse = (response) => {
  const contentType = response.headers.get('content-type');
  return contentType && contentType.includes('application/json');
};

2. Caching Validated Responses

// ✅ Cache validation results to avoid repeated checks
const responseCache = new Map();

3. Early Error Detection

// ✅ Detect and handle errors early in the request cycle
// Implement validation at the earliest possible stage

Security Considerations

1. Validate Content Before Processing

// ✅ Always validate response content before processing
// Prevent injection attacks through malformed responses

2. Sanitize Unexpected Responses

// ✅ Sanitize any HTML responses that shouldn't be received
// Log security incidents appropriately

3. Implement Proper CORS Headers

// ✅ Ensure proper CORS configuration for API endpoints
// Prevent unauthorized cross-origin requests

Testing JSON Parsing

1. Unit Tests for Error Cases

// ✅ Test with HTML responses to ensure proper handling
test('handles HTML response gracefully', async () => {
  // Mock server to return HTML instead of JSON
  // Verify proper error handling
});

2. Integration Tests

// ✅ Test full request/response cycle
// Verify that API endpoints return proper JSON

3. End-to-End Tests

// ✅ Test user flows that involve API calls
// Ensure error states are handled properly in UI

Alternative Solutions

1. Custom Response Parser

// ✅ Create a custom parser that handles both JSON and HTML responses
const smartParser = async (response) => {
  const text = await response.text();
  if (text.trim().startsWith('<')) {
    return { type: 'html', content: text };
  }
  return { type: 'json', content: JSON.parse(text) };
};

2. Middleware Approach

// ✅ Implement middleware to handle response validation
// Apply consistently across all API calls

3. Server-Side Fixes

// ✅ Fix the root cause on the server side
// Ensure API routes always return proper JSON

Migration Checklist

  • Identify all API calls that could return HTML instead of JSON
  • Implement response type validation before JSON parsing
  • Update server-side routes to return proper JSON errors
  • Add error handling for HTML responses
  • Test all API endpoints return correct content types
  • Verify proxy configurations preserve content types
  • Update documentation for team members
  • Add unit tests for error scenarios

Conclusion

The ‘Unexpected token < in JSON at position 0’ error occurs when applications expect JSON responses but receive HTML content instead. By following the solutions provided in this guide—whether through proper response validation, server-side configuration, error handling patterns, or framework-specific approaches—you can ensure your React, Angular, and Vue applications handle API responses safely and gracefully.

The key is to always validate response types before parsing JSON, implement proper error handling for unexpected responses, configure server routes correctly, and test thoroughly with various response scenarios. With proper implementation of these patterns, your applications will be resilient to this common JSON parsing error and provide a better user experience.

Remember to always check response content types, implement proper error handling, validate server configurations, and test edge cases to create robust applications that handle unexpected responses gracefully.

Gautam Sharma

About Gautam Sharma

Full-stack developer and tech blogger sharing coding tutorials and best practices

Related Articles

Tutorials

Fix: Build succeeded but site shows blank page in React Angular Vue

Complete guide to fix blank page issues after successful builds in React, Angular, and Vue applications. Learn how to debug and resolve blank page errors with practical solutions.

January 8, 2026
TypeScript

Fix: Type 'string' is not assignable to type error in TypeScript - Complete Guide

Complete guide to fix 'Type string is not assignable to type' TypeScript errors. Learn how to resolve type assignment issues with practical solutions, type casting, and best practices for TypeScript development.

January 8, 2026
React

Fix: Invalid React hook call. Hooks can only be called inside of the body of a function component

Learn how to fix the 'Invalid hook call' error in React. This guide covers all causes, solutions, and best practices for proper React hook usage with step-by-step examples.

January 1, 2026