fix: client data provider falls back to HTML extraction for SSG hosts#41
Merged
Conversation
makeClientLayer's data provider was unconditionally parsing ?_data=1 responses as JSON. That works against the SSR HTTP handler but silently breaks on any static-file host: static servers ignore the query string and return the target page's HTML shell, response.json() throws, .pipe(Effect.orDie) kills the fiber, Outlet goes blank with no console error. Now the provider checks Content-Type. JSON responses parse as before; anything else is treated as HTML, and the embedded window.__EFFEX_DATA__ blob (which every SSG'd page has via generateDocument) is extracted via regex and JSON.parsed. serializeForHtml's <>& escapes are already JSON-native (\uXXXX), so the round-trip is lossless. Callers don't need to change anything — SSG projects using makeClientLayer just start navigating correctly. Test coverage: unit-tests the extraction against generateDocument's real output, including payloads containing </script>, ampersands, and angle brackets, plus the no-data and malformed-json cases. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Effect.orDie was making genuinely-failed fetches (network errors, malformed responses, etc.) invisible in the console — the exact reason the SSG navigation bug went undiagnosed. Log the error with route context before Effect.orDie so devtools + error trackers pick it up. Also warn (not error) when the HTML fallback succeeds but no __EFFEX_DATA__ blob is present in the response. Non-fatal — the route may legitimately have no loader data — but a broken build/deploy should be visible instead of quietly serving undefined data. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Deploying effex-api with
|
| Latest commit: |
2aeb30a
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://0b185a65.effex-api.pages.dev |
| Branch Preview URL: | https://fix-static-data-provider-fal.effex-api.pages.dev |
Deploying effex with
|
| Latest commit: |
2aeb30a
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://b6a77d5a.effex.pages.dev |
| Branch Preview URL: | https://fix-static-data-provider-fal.effex.pages.dev |
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fix client-side navigation on SSG (static-file-hosted) sites — clicking a
<Link>updated the URL but blanked the<Outlet>, with no error in the console.Root cause
makeClientLayer's data provider was doing:```ts
const response = yield* Effect.tryPromise(() => fetch(`${path}?${qs.toString()}`));
const json = yield* Effect.tryPromise(() => response.json());
return json as unknown as RouteDataService;
```
That works against the SSR HTTP handler (which returns JSON for `?_data=1` requests). It silently breaks on any static host — static file servers ignore the query string and return the target page's HTML shell. `response.json()` throws on that HTML, `.pipe(Effect.orDie)` converts the failure into a defect and kills the fiber. The Outlet's data effect dies before rendering; the URL has already updated because `pushPath` runs before the fetch.
No error surfaces because the fiber's defect is swallowed by the ambient runtime.
Fix
The provider now inspects the response's `Content-Type` header:
The escapes `serializeForHtml` emits (`\u003c` / `\u003e` / `\u0026`) are native JSON escapes, so the extract → `JSON.parse` round-trip is lossless — including payloads that contain `</script>` or ampersands.
Callers
No config change required. Projects using `Platform.makeClientLayer(router)` just start navigating correctly after upgrading.
Test plan
Note
Two knobs I considered but didn't touch:
🤖 Generated with Claude Code