The form submits, the spinner appears, and the console says unexpected token < in JSON at position 0. The page itself loads fine, which is why the first instinct is to blame the parser or the tunnel. Neither is usually involved: the submission is a different request from the page load, and it came back with something that is not JSON. Work it in the order a browser actually reveals it, because the four possible answers need four different fixes.

Step 1: capture the submission that really happened

Open the Network panel before you submit, with the log set to preserve entries across navigation, and then make one harmless test submission on an account you are allowed to use. Select the request the form itself initiated rather than the page's document request.

Be careful with filters. An XHR or Fetch filter is convenient, but if the framework still submits through a hidden iframe or a dynamically created form, filtering will hide the only request that matters. Submit once unfiltered first and look at what appeared.

For the request you selected, record six things before interpreting anything: method, full URL including the query string, status, the redirect chain, the response Content-Type, and the first lines of the body. The Chrome DevTools Network panel reference covers the Headers, Response and Initiator views where those live, and the equivalent panels in other browsers show the same fields.

Step 2: confirm it is the request you think it is

A surprising number of JSON errors are actually routing errors. If the submit handler throws before it calls preventDefault, the browser performs a native form POST to whatever the action attribute says, and the server answers with a full HTML page. The handler never runs, so no JSON was ever requested.

Compare the URL in the panel with the URL your JavaScript calls. Look for a redirect that ends somewhere else, a missing or extra trailing slash, a method mismatch where the handler sends GET and the endpoint accepts POST, or a form posting to a relative path from a page that moved. Follow the redirect chain to the end and note the final URL, not the first one.

Then check the initiator. If the request came from a document navigation rather than your script, you have already found the problem, and no change to a response parser will help.

Step 3: sort the outcome into four categories

Every result belongs to exactly one of these, and the category decides where you look next.

What you seeCategoryWhere to look
Request never finishes, TLS or connection errorNo usable responseEndpoint reachability, certificate, front-end timeout
Request completes, script gets an opaque failureAccess blockedCross-origin policy, credentials mode, missing response header
200 with an HTML body or a login formWrong representationRedirect, expired session, route fallback, error handler
Body begins with a notice then valid JSONWrong representationServer-side warning output before serialisation
Valid JSON, framework rejects itContract mismatchEnvelope shape, field names, value types

The middle rows are the ones people misdiagnose, because the status is 200. A sign-in page reached after a redirect is a perfectly good HTTP response and a terrible JSON document. A content type of application/json does not repair that: it is a label, and a mislabelled body still fails to parse. Access blocked is the row that looks like a completed request: the panel shows a status, but cross-origin policy withholds the body, so for the calling script it is as unusable as a timeout.

The fourth row is equally common on older PHP and framework stacks. A deprecation notice printed before the encoded payload produces a body that no JSON parser will accept, and the notice is often the whole explanation.

Step 4: read the parser error literally

The error text names the first character that broke the parse, which usually identifies the category without any guessing.

Error text (Chrome/V8)What it means
Unexpected token < in JSON at position 0The body starts with markup: an HTML page or an error document
Unexpected end of JSON inputEmpty body, or the response was truncated
Unexpected token o in JSON at position 1You parsed something already parsed, typically an object rather than text
Unexpected token uThe body starts with the bare word undefined, usually an empty response rendered as a string

An empty body with a 200 is its own case: the endpoint ran, produced nothing, and something downstream treated that as success. That is an application contract failure rather than a transport failure, and it belongs in the next step rather than in the proxy. Firefox and Safari word the same four failures differently, so match on the character or on the empty body rather than on the exact string.

Step 5: fix the layer the evidence points at

For a routing problem, fix the request: correct the action, stop the handler from falling through, make the endpoint and method match, and re-test. For a representation problem, fix what produced the body, and find out why an error document is being returned where data was expected. For a contract problem, read the framework's actual expected structure for the version you run, and shape the response to it instead of loosening the client.

An access-blocked response needs its own pass, because the request itself may be correct and the body may never reach your code. Compare the response's Access-Control-Allow-Origin with the exact Origin the browser sent, and remember that a wildcard does not satisfy a credentialed request. Pair credentials: 'include' on the client with Access-Control-Allow-Credentials: true on the server, and check whether the call used no-cors mode, where an opaque body is the documented result rather than a bug.

Replacing the parser with something permissive is the one fix to avoid. Evaluating the response as code, or stripping text until it parses, hides the contract mismatch and adds an injection surface in exchange for silence. Equally, checking the status separately from the body is required: a 200 carrying an application-level error object is normal in many APIs and must be handled by reading the payload.

When you need help from someone else, share a minimal record — method, URL, status, final URL after redirects, response content type, body shape, and the expected contract — rather than a raw export. Network archives contain cookies, tokens, query strings and the personal data that was in the form, and an automatic sanitiser is not a review.

  • Capture the request unfiltered before changing any code.
  • Confirm the initiator was your script and the final URL is your endpoint.
  • Classify the response before touching the parser.
  • Fix routing, access, representation or contract, and never by relaxing the parser.

ບົດຄວາມທີ່ກ່ຽວຂ້ອງ