remix
remix copied to clipboard
Single Fetch: json serialization is unstable
Reproduction
https://stackblitz.com/edit/remix-run-remix-ioniz1?file=app%2Froutes%2F_index.tsx
System Info
System:
OS: Linux 5.0 undefined
CPU: (8) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
Memory: 0 Bytes / 0 Bytes
Shell: 1.0 - /bin/jsh
Binaries:
Node: 18.18.0 - /usr/local/bin/node
Yarn: 1.22.19 - /usr/local/bin/yarn
npm: 10.2.3 - /usr/local/bin/npm
pnpm: 8.15.6 - /usr/local/bin/pnpm
npmPackages:
@remix-run/dev: * => 2.9.2
@remix-run/node: * => 2.9.2
@remix-run/react: * => 2.9.2
@remix-run/serve: * => 2.9.2
vite: ^5.1.0 => 5.2.11
Used Package Manager
npm
Expected Behavior
No error
Actual Behavior
Error:
Warning: Prop `dangerouslySetInnerHTML` did not match. Server: "[{\"@context\":\"https://schema.org\",\"name\":\"Hello, World!\"}]" Client: "[{\"name\":\"Hello, World!\",\"@context\":\"https://schema.org\"}]"
Note that the reason I am passing meta as loader property is because it was described as solution to https://github.com/remix-run/remix/issues/9418
The issue appears because serialized JSON values have different property order.
I can somewhat workaround this by changing my server-side object declaration to match what client renders, i.e.
{
name: 'Hello, World!',
'@context': 'https://schema.org',
},
But this is finicky and problematic because I have ESLint rule that auto sorts properties alphabetically.
This looks like it's coming from internal JSON.parse behavior that must order numeric object keys:
@jacob-ebey What do you think about prefixing those with _ or something in turbo-stream to make them non-numeric?
I think in my case it is the properties that start with @.
It's not related to the name of the key. It's due to the reuse of a name property that was previously used. turbo-stream dedupes keys and values to keep the payload minimal, and it tracks them basically with an incrementing integer. When you return something like the following, the first occurrence of name gets assigned a lower integer than @context and then the second occurrence of name re-uses the earlier/lower id.
export const loader = () => {
return {
meta: [
{
name: 'description',
// ^ `name` here gets assigned a lower numeric id since it's earlier
content: 'Welcome to Remix!',
},
{
'script:ld+json': [
{
'@context': 'https://schema.org',
// ^ `@context` here gets assigned a higher numeric id since it's later
name: 'Hello, World!',
// ^ `name` here reuses the earlier (lower) numeric id
},
],
},
],
};
};
This results in the problem I described above where we stringify an object with a higher numeric key, followed by a lower numeric key - and JSON.parse re-orders them when parsing, moving name before @context
I believe I have addressed this with [email protected]. I can no longer reproduce it in the stackblitz: https://stackblitz.com/edit/remix-run-remix-eliwev?file=package.json
I think the serialization is still unstable in a slightly different manner - just not surfacing this specific meta/json+ld issue. Here's a simplified repro on 2.2.3: https://stackblitz.com/edit/remix-run-remix-bbrwkg?file=app%2Froutes%2F_index.tsx
Added some better tests and addressed it here: https://github.com/jacob-ebey/turbo-stream/pull/45
👍 Awesome - the above stackblitz is fixed with [email protected] via the solution in https://github.com/remix-run/remix/issues/9421#issuecomment-2135549260.
[email protected] will be the default version in Remix 2.11.2 (currently in prerelease, stable release later this week) so you won't need your own turbo-stream dependency in your package.json once upgrading 👍
Thank you!