Skip to content

Explain when a Commerce route answers with the app's HTML page - #1530

Merged
sbolinger-godaddy merged 2 commits into
mainfrom
storefront-non-json-api-errors
Nov 5, 2026
Merged

sbolinger-godaddy merged 2 commits into
mainfrom
storefront-non-json-api-errors

Conversation

@sbolinger-godaddy

@sbolinger-godaddy sbolinger-godaddy commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

A template with no connected store showed Unexpected token '<', "<!doctype "... is not valid JSON in place of its catalog. Its server never mounted /api/commerce, so the SSR catch-all answered GET /api/commerce/config with index.html and a 200. The storefront only read the body of failed responses, so it passed the page to response.json().

  • A text/html response below 500 is now an unreachable store, whether it's a 200 from a single-page app fallback or an HTML 404 page. checkedFetch throws an ApiError with status 502, so an HTML 404 never clears the saved cart, and the new apiUnavailable text: "The store could not be reached. Please retry." in English and "La boutique est injoignable. Veuillez réessayer." in French. The check is in checkedFetch, so it covers every request() call and the cart's add path.
  • It never shows sample products. As the server contract requires, a missing route is an error, not unbound, so a connected store with a broken deployment never shows fake products.
  • Development builds name the route and the fix, once per route per page load, because configuration polling repeats the request:
    [gd-commerce-storefront] /api/commerce/config returned an HTML page instead of JSON. Mount the commerce routes at /api/commerce on this origin, before any static file or single-page app fallback route.
  • Docs:
    • The server README has a new section, "A site before its store connects". It covers createCommerceCatalogRouter, an explicit readConnectionState(), and mounting the routes before static and fallback routes.
    • The storefront README has a troubleshooting entry for this error.
    • The server API contract says a text/html success is treated as a failure.

The server example reads the connection state from an explicit setting. While fixing the template, an agent inferred unbound from a missing GODADDY_STORE_ID, which both packages forbid: a connected store that lost the variable would quietly show samples.

Screenshots

Before
before-catalog-json-error

After
after-catalog-unavailable

Changeset

  • Changeset added (@godaddy/gd-commerce-storefront patch). The server package changes are docs only.

Test Plan

  • Unit tests (Vitest), 3 new in storefront.test.tsx. All fail without the change:
    • request() and addToCart turn an HTML 200 into an ApiError with apiUnavailable text, and the console message appears once per route.
    • The catalog shows the new message, never the parse error, and no samples.
    • A cart read answered by an HTML 404 keeps the saved cart ID.
  • pnpm --filter @godaddy/gd-commerce-storefront build, typecheck, lint and test pass (185 tests), and the example app builds.
  • Commerce component test app, with packed builds of main and this branch:
    • A new html-api check passes 5 of 5 on this branch and fails 5 of 5 on main. It covers the catalog, sample catalog, product page, retry once routes answer, and one console message per page load after more /config requests.
    • The existing suites pass on this branch: smoke 19, parts 13, walkthrough 5, french 5, boutique 4. Routine passes 4 of 5; its "template samples before a store connects" check fails the same way on main.

When a host never mounts /api/commerce, its single-page app or SSR fallback
answers with index.html and a 200, and the storefront showed a JSON parse
error: Unexpected token '<', "<!doctype "... is not valid JSON.

A successful text/html response is now a failure with readable text, "The
store could not be reached. Please retry.", in English and French. It never
shows sample products. Development builds log the route once and say where
to mount the Commerce routes. The server README shows the setup for a site
before its store connects, and the storefront README has a troubleshooting
entry for the error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sbolinger-godaddy
sbolinger-godaddy requested a review from a team as a code owner November 4, 2026 21:15
@changeset-bot

changeset-bot Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 21cc7d5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@godaddy/gd-commerce-storefront Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment thread packages/commerce-storefront/src/api.ts Resolved
Comment thread packages/commerce-storefront/src/api.ts Resolved
An HTML 404 from a host without a single-page app fallback read as an
expired cart and cleared the saved cart ID. Report every HTML response
below 500 as apiUnavailable with status 502, so cart hydration keeps the
ID and host code branching on status sees a failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@pbennett1-godaddy pbennett1-godaddy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both points are fixed in 21cc7d5. I found no new issues. Build, tests, typecheck and lint pass on the PR head.

@sbolinger-godaddy
sbolinger-godaddy merged commit 8f69cfd into main Nov 5, 2026
3 checks passed
@sbolinger-godaddy
sbolinger-godaddy deleted the storefront-non-json-api-errors branch November 5, 2026 18:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants