Skip to content

add orjson - #6116

Draft
benedikt-bartscher wants to merge 47 commits into
reflex-dev:mainfrom
benedikt-bartscher:try-orjson
Draft

add orjson#6116
benedikt-bartscher wants to merge 47 commits into
reflex-dev:mainfrom
benedikt-bartscher:try-orjson

Conversation

@benedikt-bartscher

Copy link
Copy Markdown
Contributor

No description provided.

@codspeed-hq

codspeed-hq Bot commented Feb 6, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 26 untouched benchmarks
🆕 2 new benchmarks
⏩ 8 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
🆕 Simulation test_process_event_wire[large_delta] N/A 31.8 ms N/A
🆕 Simulation test_process_event_wire[small_delta] N/A 3.6 ms N/A

Comparing benedikt-bartscher:try-orjson (544c5c5) with main (333b78b)

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@benedikt-bartscher
benedikt-bartscher marked this pull request as ready for review February 7, 2026 13:38
@greptile-apps

greptile-apps Bot commented Feb 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds optional orjson support for JSON handling. The main changes are:

  • Faster JSON helpers for generated files and socket payloads.
  • Frontend parsing support for non-finite float sentinels.
  • Upload progress serialization through the new socket JSON helper.
  • Serializer fallback behavior for values orjson cannot safely handle.
  • Tests and dependency metadata for the new optional extra.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
reflex/app.py Adds the socket JSON codec and routes event decoding through the shared orjson helper.
packages/reflex-components-core/src/reflex_components_core/core/_upload.py Uses the shared orjson helpers for upload event args and streamed state updates.
packages/reflex-base/src/reflex_base/utils/format.py Adds orjson-backed dump/load helpers, socket-safe serialization, and fallback paths.
packages/reflex-base/src/reflex_base/.templates/web/utils/helpers/json.js Adds frontend JSON parsing that restores non-finite float sentinel values.

Reviews (21): Last reviewed commit: "useless orjson here" | Re-trigger Greptile

@greptile-apps greptile-apps Bot 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.

5 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment thread reflex/app.py Outdated
@adhami3310

Copy link
Copy Markdown
Member

the issue with orjson is that we lose NaN handling which is relevant for numpy, which is why we were using JSON5 as well

@benedikt-bartscher

benedikt-bartscher commented Feb 12, 2026

Copy link
Copy Markdown
Contributor Author

Yeah, that's why i did not update it in all places

benedikt-bartscher and others added 3 commits February 24, 2026 21:41
…llback

Happy path uses native JSON.parse; the catch rewrites Python"s bare
Infinity/-Infinity/NaN tokens outside string literals before retrying,
so only payloads that actually contain specials pay the extra cost.
NaN has no JSON literal and becomes null (matches JSON.stringify).
…o null

Swap bare NaN for a sentinel string before JSON.parse and revive it back
to a real NaN so Python-side float(nan) round-trips to the frontend.
@benedikt-bartscher

Copy link
Copy Markdown
Contributor Author

related #6339

@benedikt-bartscher
benedikt-bartscher requested a review from a team as a code owner April 25, 2026 22:13
Apply orjson_dumps/orjson_loads helpers to the new reflex_base
package locations after upstream's restructuring.
@benedikt-bartscher
benedikt-bartscher marked this pull request as draft April 26, 2026 16:50
FarhanAliRaza added a commit to FarhanAliRaza/reflex that referenced this pull request Jul 31, 2026
Serialization previously walked every payload with
_replace_non_finite_floats before every socket emit to protect NaN/Infinity
floats (orjson silently emits null for them) and to escape
sentinel-colliding strings. The walk dominated serialization cost (~95% on
a 290KB payload) and ran even in the stdlib fallback, making default
installs slower than before reflex-dev#6116.

Both backends now dump once and decide from the output bytes whether the
walk is needed at all:

- orjson: None, NaN and Infinity all serialize to exactly the token null,
  so output without null provably lost no non-finite floats; output without
  the sentinel prefix has no colliding strings. Only when either substring
  appears is the payload walked and re-dumped. The default= callback is
  split into a non-walking pass-1 variant and a walking pass-2 variant so
  StateUpdate-wrapped payloads keep the fast path.
- stdlib (no orjson): bare NaN/Infinity tokens are restored by the
  frontend's bare-token rewriter, so only a sentinel-prefix hit forces the
  walk. Restores default-install performance to parity with pre-reflex-dev#6116.

Frontend mirror of the same idea: passing a reviver to JSON.parse disables
the engine's native fast parser (measured 4-12x slower in Chromium), so
parseNonFiniteAwareJSON only applies the reviver when the payload actually
contains the sentinel prefix. The upload NDJSON parser gains the bare-token
rewriter retry it was missing.

Also: hoist the orjson import to module level (was per-call on the compile
hot path), make orjson_dumps fall back to stdlib for indent widths other
than 2 instead of silently coercing, and preserve kwargs in its TypeError
fallback.

Serialize-only medians vs stdlib baseline (1000x10 table delta): 1.91ms ->
431us with orjson, parity without. Browser decode of the same delta: 2.05ms
-> 506us.
benedikt-bartscher and others added 5 commits July 31, 2026 20:31
test_process_event_wire measures the full receive -> process -> serialize
round trip: raw event JSON decoded via orjson_loads (as socket.io's decoder
does), events processed through BaseStateEventProcessor with a real
StateManagerMemory, and each delta serialized through orjson_dumps_socket
the way App._setup_state wires socket.io's encoder. Parametrized with a
small (5-row) and large (500-row) table delta so JSON parse/dump cost is
part of the measured path, which no existing benchmark covered.
Serialization previously walked every payload with
_replace_non_finite_floats before every socket emit to protect NaN/Infinity
floats (orjson silently emits null for them) and to escape
sentinel-colliding strings. The walk dominated serialization cost (~95% on
a 290KB payload) and ran even in the stdlib fallback, making default
installs slower than before reflex-dev#6116.

Both backends now dump once and decide from the output bytes whether the
walk is needed at all:

- orjson: None, NaN and Infinity all serialize to exactly the token null,
  so output without null provably lost no non-finite floats; output without
  the sentinel prefix has no colliding strings. Only when either substring
  appears is the payload walked and re-dumped. The default= callback is
  split into a non-walking pass-1 variant and a walking pass-2 variant so
  StateUpdate-wrapped payloads keep the fast path.
- stdlib (no orjson): bare NaN/Infinity tokens are restored by the
  frontend's bare-token rewriter, so only a sentinel-prefix hit forces the
  walk. Restores default-install performance to parity with pre-reflex-dev#6116.

Frontend mirror of the same idea: passing a reviver to JSON.parse disables
the engine's native fast parser (measured 4-12x slower in Chromium), so
parseNonFiniteAwareJSON only applies the reviver when the payload actually
contains the sentinel prefix. The upload NDJSON parser gains the bare-token
rewriter retry it was missing.

Also: hoist the orjson import to module level (was per-call on the compile
hot path), make orjson_dumps fall back to stdlib for indent widths other
than 2 instead of silently coercing, and preserve kwargs in its TypeError
fallback.

Serialize-only medians vs stdlib baseline (1000x10 table delta): 1.91ms ->
431us with orjson, parity without. Browser decode of the same delta: 2.05ms
-> 506us.
The stdlib fallbacks (no orjson, or orjson TypeError e.g. >64-bit ints)
walked the original packet, but _replace_non_finite_floats cannot traverse
custom objects like the StateUpdate every real socket payload wraps.
Strings produced by serializers.serialize therefore shipped unescaped, and
the frontend reviver silently corrupted user data that collided with a
sentinel: "__reflex_nan__" became NaN, "__reflex_esc__x" became "x".

Route both fallbacks through _json_dumps_socket_fallback, whose re-dump
passes a walking default= callback so serializer output is escaped the
same way the orjson path's _default_walked already does. The fast path
(single dump + prefix scan) is unchanged.

Regression tests use the real wire shape ["event", StateUpdate(delta)]
and fail against the previous implementation on all three paths.
Both orjson.dumps passes fell back to the same stdlib path on TypeError;
one try block covering the whole orjson section says that once. Behavior
unchanged: any payload orjson cannot represent (e.g. int > 64-bit) takes
_json_dumps_socket_fallback regardless of which pass it surfaced in.

@cubic-dev-ai cubic-dev-ai Bot 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.

3 issues found across 35 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/reflex-base/src/reflex_base/plugins/shared_tailwind.py">

<violation number="1" location="packages/reflex-base/src/reflex_base/plugins/shared_tailwind.py:94">
P2: Tailwind theme values or plugin arguments containing `NaN`/`Infinity` now become `null`, silently changing the generated JavaScript configuration. Since this output is JavaScript rather than strict JSON, consider retaining stdlib `json.dumps` here or using a serializer that preserves these valid JS numeric tokens.</violation>
</file>

<file name="packages/reflex-base/src/reflex_base/.templates/web/utils/state.js">

<violation number="1" location="packages/reflex-base/src/reflex_base/.templates/web/utils/state.js:493">
P2: State values from a custom `App.sio` can be corrupted: legitimate `__reflex_nan__`, `__reflex_inf__`, or `__reflex_neg_inf__` strings are revived even though that server was never configured to escape collisions. Consider configuring provided `AsyncServer` instances with the sentinel-aware serializer, or marking sentinel-encoded packets before applying this reviver.</violation>
</file>

<file name="packages/reflex-base/src/reflex_base/utils/format.py">

<violation number="1" location="packages/reflex-base/src/reflex_base/utils/format.py:724">
P1: Reflex serializers are bypassed whenever orjson natively supports a value: `orjson_dumps` changes datetime formatting, and both fast paths skip custom Enum serializers. Route these native types through the serializer registry or retain `json_dumps` for them so generated values and socket deltas preserve existing semantics.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread packages/reflex-site-shared/src/reflex_site_shared/lib/meta/meta.py Outdated
Comment thread reflex/utils/prerequisites.py
import json
import os
import re
from functools import lru_cache

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

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.

P1: Reflex serializers are bypassed whenever orjson natively supports a value: orjson_dumps changes datetime formatting, and both fast paths skip custom Enum serializers. Route these native types through the serializer registry or retain json_dumps for them so generated values and socket deltas preserve existing semantics.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/reflex-base/src/reflex_base/utils/format.py, line 724:

<comment>Reflex serializers are bypassed whenever orjson natively supports a value: `orjson_dumps` changes datetime formatting, and both fast paths skip custom Enum serializers. Route these native types through the serializer registry or retain `json_dumps` for them so generated values and socket deltas preserve existing semantics.</comment>

<file context>
@@ -683,6 +688,210 @@ def json_dumps(obj: Any, **kwargs) -> str:
+        option |= orjson.OPT_SORT_KEYS
+
+    try:
+        return orjson.dumps(obj, option=option or None).decode()
+    except TypeError:
+        # json_dumps handles large integers and registered custom types.
</file context>
Fix with cubic

Comment thread reflex/utils/frontend_skeleton.py Outdated
@@ -91,7 +91,7 @@ def tailwind_config_js_template(
Returns:

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

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.

P2: Tailwind theme values or plugin arguments containing NaN/Infinity now become null, silently changing the generated JavaScript configuration. Since this output is JavaScript rather than strict JSON, consider retaining stdlib json.dumps here or using a serializer that preserves these valid JS numeric tokens.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/reflex-base/src/reflex_base/plugins/shared_tailwind.py, line 94:

<comment>Tailwind theme values or plugin arguments containing `NaN`/`Infinity` now become `null`, silently changing the generated JavaScript configuration. Since this output is JavaScript rather than strict JSON, consider retaining stdlib `json.dumps` here or using a serializer that preserves these valid JS numeric tokens.</comment>

<file context>
@@ -91,7 +91,7 @@ def tailwind_config_js_template(
         The Tailwind config template.
     """
-    import json
+    from reflex_base.utils.format import orjson_dumps
 
     # Extract parameters
</file context>
Fix with cubic

Comment thread reflex/app.py Outdated
Comment thread packages/reflex-base/news/6116.performance.md Outdated
Comment thread packages/reflex-base/src/reflex_base/.templates/web/utils/helpers/upload.js Outdated
Comment thread packages/reflex-base/src/reflex_base/config.py Outdated
Comment thread packages/reflex-base/src/reflex_base/utils/format.py Outdated

@cubic-dev-ai cubic-dev-ai Bot 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.

7 issues found across 35 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/reflex-components-core/pyproject.toml">

<violation number="1" location="packages/reflex-components-core/pyproject.toml:11">
P1: Publishing this package can fail because the new minimum dependency is a dev build (`*.dev0`) instead of a publishable release. Using a stable minimum version for `reflex-base` keeps `--check-dev-pins` green and avoids release-time blocking.</violation>
</file>

<file name="packages/reflex-base/src/reflex_base/.templates/web/utils/helpers/json.js">

<violation number="1" location="packages/reflex-base/src/reflex_base/.templates/web/utils/helpers/json.js:53">
P3: Malformed payloads currently do an unnecessary second full parse attempt before failing. The unconditional retry in `catch` increases CPU work on hot paths like streaming upload chunks; retrying only when the rewrite actually changes `str` keeps non-finite support without the extra failure-path cost.</violation>
</file>

<file name="packages/reflex-base/src/reflex_base/utils/format.py">

<violation number="1" location="packages/reflex-base/src/reflex_base/utils/format.py:763">
P2: Large integer values can be silently corrupted when orjson is installed, because `orjson_loads` always dispatches to `orjson.loads` even for payloads that may contain >64-bit ints. A safe fallback path (or an explicit guard for known large-int payloads) would keep behavior aligned with the documented expectation and avoid precision loss.</violation>

<violation number="2" location="packages/reflex-base/src/reflex_base/utils/format.py:894">
P2: The socket fast-path shortcut is defeated by the very payloads this PR is optimizing. The `b"null" not in out` check is meant to catch NaN/Inf (which orjson serializes as `null`), but it also fires for any genuine `None` in the state delta — common for unset/optional vars. In those cases the code pays for a full `_replace_non_finite_floats` graph walk plus a second `orjson.dumps`, roughly doubling the serialization cost of typical state-update packets and undercutting the performance goal the news file states. Consider detecting non-finite floats precisely (e.g. a dedicated scan that distinguishes NaN/Inf from plain null) instead of relying on the raw `null` byte pattern, so common null-containing payloads still hit the single-pass path.</violation>
</file>

<file name="reflex/utils/frontend_skeleton.py">

<violation number="1" location="reflex/utils/frontend_skeleton.py:368">
P2: A damaged `.web/package.json` can now abort lockfile sync instead of being overwritten. The new direct UTF-8 read in the equality check is unguarded, so decode errors stop the recovery path.</violation>

<violation number="2" location="reflex/utils/frontend_skeleton.py:424">
P2: Malformed-encoding `package.json` files can now crash initialization instead of being treated as empty. The new UTF-8 decode path raises `UnicodeDecodeError`, but the handler only catches JSON/OSError, so this code path no longer matches the function’s fallback behavior.</violation>
</file>

<file name="packages/reflex-base/src/reflex_base/compiler/templates.py">

<violation number="1" location="packages/reflex-base/src/reflex_base/compiler/templates.py:535">
P3: Heads-up on reproducibility: these generated/config files (`.web/package.json`, `react-router.config.js`, the inlined clientStorage/isDevMode JS, and `cpu_info.json`) are now rendered with `orjson_dumps`, whose output depends on whether the optional `orjson` extra is installed. With orjson present you get compact UTF-8 (`{"a":1}`); without it, the fallback `json_dumps` emits spaced output (`{"a": 1}`) and different float handling. The same source tree will therefore produce byte-different artifacts across environments (and the strict string equality in `sync_root_package_json_to_web` can then see package.json as 'changed' when the extra flips). Consider forcing a canonical format for artifacts written to disk (e.g. always route these through an explicit `orjson_dumps(..., indent=2)`/fixed separator path, or serialize package.json/config deterministically regardless of optional-dependency presence) so outputs are stable.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

requires-python = ">=3.10"
dependencies = [
"reflex-base >= 0.9.7",
"reflex-base >= 0.9.8.post37.dev0",

@cubic-dev-ai cubic-dev-ai Bot Aug 4, 2026

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.

P1: Publishing this package can fail because the new minimum dependency is a dev build (*.dev0) instead of a publishable release. Using a stable minimum version for reflex-base keeps --check-dev-pins green and avoids release-time blocking.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/reflex-components-core/pyproject.toml, line 11:

<comment>Publishing this package can fail because the new minimum dependency is a dev build (`*.dev0`) instead of a publishable release. Using a stable minimum version for `reflex-base` keeps `--check-dev-pins` green and avoids release-time blocking.</comment>

<file context>
@@ -8,7 +8,7 @@ authors = [{ name = "Khaleel Al-Adhami", email = "khaleel@reflex.dev" }]
 requires-python = ">=3.10"
 dependencies = [
-    "reflex-base >= 0.9.7",
+    "reflex-base >= 0.9.8.post37.dev0",
     "reflex-components-lucide >= 0.9.0",
     "reflex-components-sonner >= 0.9.0",
</file context>
Suggested change
"reflex-base >= 0.9.8.post37.dev0",
"reflex-base >= 0.9.8",
Fix with cubic

"""
if orjson is None:
return json.loads(data)
return orjson.loads(data)

@cubic-dev-ai cubic-dev-ai Bot Aug 4, 2026

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.

P2: Large integer values can be silently corrupted when orjson is installed, because orjson_loads always dispatches to orjson.loads even for payloads that may contain >64-bit ints. A safe fallback path (or an explicit guard for known large-int payloads) would keep behavior aligned with the documented expectation and avoid precision loss.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/reflex-base/src/reflex_base/utils/format.py, line 763:

<comment>Large integer values can be silently corrupted when orjson is installed, because `orjson_loads` always dispatches to `orjson.loads` even for payloads that may contain >64-bit ints. A safe fallback path (or an explicit guard for known large-int payloads) would keep behavior aligned with the documented expectation and avoid precision loss.</comment>

<file context>
@@ -683,6 +688,221 @@ def json_dumps(obj: Any, **kwargs) -> str:
+    """
+    if orjson is None:
+        return json.loads(data)
+    return orjson.loads(data)
+
+
</file context>
Fix with cubic

Comment thread reflex/utils/frontend_skeleton.py Outdated
Comment thread reflex/utils/frontend_skeleton.py Outdated

try:
out = orjson.dumps(obj, default=_default_fast, option=_ORJSON_SOCKET_OPTS)
if b"null" not in out and _SENTINEL_PREFIX_BYTES not in out:

@cubic-dev-ai cubic-dev-ai Bot Aug 4, 2026

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.

P2: The socket fast-path shortcut is defeated by the very payloads this PR is optimizing. The b"null" not in out check is meant to catch NaN/Inf (which orjson serializes as null), but it also fires for any genuine None in the state delta — common for unset/optional vars. In those cases the code pays for a full _replace_non_finite_floats graph walk plus a second orjson.dumps, roughly doubling the serialization cost of typical state-update packets and undercutting the performance goal the news file states. Consider detecting non-finite floats precisely (e.g. a dedicated scan that distinguishes NaN/Inf from plain null) instead of relying on the raw null byte pattern, so common null-containing payloads still hit the single-pass path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/reflex-base/src/reflex_base/utils/format.py, line 894:

<comment>The socket fast-path shortcut is defeated by the very payloads this PR is optimizing. The `b"null" not in out` check is meant to catch NaN/Inf (which orjson serializes as `null`), but it also fires for any genuine `None` in the state delta — common for unset/optional vars. In those cases the code pays for a full `_replace_non_finite_floats` graph walk plus a second `orjson.dumps`, roughly doubling the serialization cost of typical state-update packets and undercutting the performance goal the news file states. Consider detecting non-finite floats precisely (e.g. a dedicated scan that distinguishes NaN/Inf from plain null) instead of relying on the raw `null` byte pattern, so common null-containing payloads still hit the single-pass path.</comment>

<file context>
@@ -683,6 +688,221 @@ def json_dumps(obj: Any, **kwargs) -> str:
+
+    try:
+        out = orjson.dumps(obj, default=_default_fast, option=_ORJSON_SOCKET_OPTS)
+        if b"null" not in out and _SENTINEL_PREFIX_BYTES not in out:
+            return out.decode()
+        return orjson.dumps(
</file context>
Fix with cubic

Comment thread packages/reflex-base/src/reflex_base/.templates/web/utils/helpers/json.js Outdated
# Ensure "type" is not duplicated since it's always set to "module"
additional_keys.pop("type", None)
return json.dumps({
return orjson_dumps({

@cubic-dev-ai cubic-dev-ai Bot Aug 4, 2026

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.

P3: Heads-up on reproducibility: these generated/config files (.web/package.json, react-router.config.js, the inlined clientStorage/isDevMode JS, and cpu_info.json) are now rendered with orjson_dumps, whose output depends on whether the optional orjson extra is installed. With orjson present you get compact UTF-8 ({"a":1}); without it, the fallback json_dumps emits spaced output ({"a": 1}) and different float handling. The same source tree will therefore produce byte-different artifacts across environments (and the strict string equality in sync_root_package_json_to_web can then see package.json as 'changed' when the extra flips). Consider forcing a canonical format for artifacts written to disk (e.g. always route these through an explicit orjson_dumps(..., indent=2)/fixed separator path, or serialize package.json/config deterministically regardless of optional-dependency presence) so outputs are stable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/reflex-base/src/reflex_base/compiler/templates.py, line 535:

<comment>Heads-up on reproducibility: these generated/config files (`.web/package.json`, `react-router.config.js`, the inlined clientStorage/isDevMode JS, and `cpu_info.json`) are now rendered with `orjson_dumps`, whose output depends on whether the optional `orjson` extra is installed. With orjson present you get compact UTF-8 (`{"a":1}`); without it, the fallback `json_dumps` emits spaced output (`{"a": 1}`) and different float handling. The same source tree will therefore produce byte-different artifacts across environments (and the strict string equality in `sync_root_package_json_to_web` can then see package.json as 'changed' when the extra flips). Consider forcing a canonical format for artifacts written to disk (e.g. always route these through an explicit `orjson_dumps(..., indent=2)`/fixed separator path, or serialize package.json/config deterministically regardless of optional-dependency presence) so outputs are stable.</comment>

<file context>
@@ -533,7 +532,7 @@ def package_json_template(
     # Ensure "type" is not duplicated since it's always set to "module"
     additional_keys.pop("type", None)
-    return json.dumps({
+    return orjson_dumps({
         "name": additional_keys.pop("name", "reflex"),
         "type": "module",
</file context>
Fix with cubic

@benedikt-bartscher
benedikt-bartscher marked this pull request as draft August 5, 2026 18:43
@benedikt-bartscher
benedikt-bartscher marked this pull request as ready for review August 6, 2026 20:12

@cubic-dev-ai cubic-dev-ai Bot 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.

1 issue found across 35 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="reflex/utils/frontend_skeleton.py">

<violation number="1" location="reflex/utils/frontend_skeleton.py:600">
P2: A persisted `package.json` with `"overrides": null` is treated as `{}` above, and this write then replaces the user's malformed locked file with framework overrides instead of preserving it. Leaving the file untouched when `overrides` is not a mapping would let the user fix or remove it before dependency resolution.

(Based on your team's feedback about preserving user-locked package.json files with malformed overrides.)</violation>
</file>

Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.

Re-trigger cubic

package_json["overrides"] = {**overrides, **constants.PackageJson.OVERRIDES}
console.debug(f"Applying framework overrides to {package_json_path}")
package_json_path.write_text(json.dumps(package_json))
package_json_path.write_text(orjson_dumps(package_json), encoding="utf-8")

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.

P2: A persisted package.json with "overrides": null is treated as {} above, and this write then replaces the user's malformed locked file with framework overrides instead of preserving it. Leaving the file untouched when overrides is not a mapping would let the user fix or remove it before dependency resolution.

(Based on your team's feedback about preserving user-locked package.json files with malformed overrides.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At reflex/utils/frontend_skeleton.py, line 600:

<comment>A persisted `package.json` with `"overrides": null` is treated as `{}` above, and this write then replaces the user's malformed locked file with framework overrides instead of preserving it. Leaving the file untouched when `overrides` is not a mapping would let the user fix or remove it before dependency resolution.

(Based on your team's feedback about preserving user-locked package.json files with malformed overrides.) </comment>

<file context>
@@ -593,14 +597,16 @@ def update_package_json_overrides() -> bool:
     package_json["overrides"] = {**overrides, **constants.PackageJson.OVERRIDES}
     console.debug(f"Applying framework overrides to {package_json_path}")
-    package_json_path.write_text(json.dumps(package_json))
+    package_json_path.write_text(orjson_dumps(package_json), encoding="utf-8")
     return True
 
</file context>

Comment thread packages/reflex-base/news/6116.performance.md Outdated
Comment thread packages/reflex-base/src/reflex_base/.templates/web/utils/helpers/json.js Outdated
Comment thread tests/units/utils/test_format_orjson.py Outdated
@benedikt-bartscher
benedikt-bartscher marked this pull request as draft August 6, 2026 21:10
benedikt-bartscher and others added 5 commits August 7, 2026 00:13
…ex-dev#6854)

* Upgrade react-router to 8.3.0

Moves the frontend from react-router 7.18.2 to 8.3.0 and adopts the new
baseline it requires:

- Node `MIN_VERSION` 22.12.0 -> 22.22.0 (`engines.node` of react-router 8)
- Drop `react-router-dom`, removed upstream in v8. Nothing in the
  framework imported it; the existing stale-package pruning in
  `_install_frontend_packages` removes it from projects on next install.

The React 19.2.7+ peer floor and Vite 7+ floor are already met by the
current 19.2.8 and 8.0.16 pins, so neither moves here. Vite stays at
8.0.16 per the existing memo-rerender note.

No other source changes were needed. Reflex sets no `future.v8_*` flags,
so the behaviors those flags now enable by default (middleware,
pass-through requests, trailing-slash-aware data requests, the Vite
Environment API build path, and `splitRouteModules`) apply without
config changes. Generated route modules export only a default component
-- no `meta`, `loader`, or `hasErrorBoundary` -- so the `meta`
`data`/`loaderData` rename, the `RouterContextProvider` context change,
and the `hasErrorBoundary` removal are all inapplicable.
`future.unstable_optimizeDeps` remains a valid v8 config key.

Verified by building and running a multi-page app (state, websocket
event round trip, `Link` client-side navigation, dynamic route, 404
fallback) in dev and prod modes, plus the embed entry's
`createMemoryRouter`/`RouterProvider` path in a host page.

Also confirmed the `patchReactRouterHmrRuntime` vite plugin (reflex-dev#6774)
still applies: its regex matches 8.3.0's hmr runtime, the plugin's
"hmr runtime changed" skip warning never fires, and the wedge it fixes
does not recur -- editing an unloaded route then a loaded one still hot
updates. That patch fails open, so a silent regex miss would have
quietly reintroduced the bug.

Adds tests pinning the upstream Node/React/Vite floors, which otherwise
live only in react-router's manifest, so a future partial bump fails
loudly instead of at install time.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZJDfyvTWhhJ25Lpch4VmS

* Correct the vite version claim in the reflex-base 0.9.8 changelog

The 0.9.8 dependency-bump entry lists `vite: 8.0.16 -> 8.2.0`, but that
bump was reverted before reflex-dev#6678 merged -- vite 8.2.0 reintroduces a
memoized-component re-render regression, which is why the pin still
carries a comment holding it at 8.0.16. The news fragment was not
updated alongside the revert, so the released notes claim an upgrade
that never shipped.

Drops that line, and drops the "(also satisfying `vite` 8.2.0's
`^8.5.23`)" aside from the same entry's postcss-override rationale: the
shipped vite is 8.0.16, which requires only `postcss@^8.5.15`. The
override's actual reason -- keeping transitive resolutions on a patched
release (>= 8.5.18) for the security advisory -- is unchanged and
retained.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZJDfyvTWhhJ25Lpch4VmS

* Drop the framework-owned postcss package.json override

`postcss` is already pinned directly in `DEV_DEPENDENCIES` (8.5.23), and
a top-level pin satisfies and dedupes every transitive requirer --
`autoprefixer` (peer `^8.1.0`), `postcss-import` (peer `^8.0.0`), and
vite's own dependency (`^8.5.15`). Verified: with the override removed a
fresh app and the docs app both still resolve exactly one `postcss@8.5.23`,
so the security-advisory floor (>= 8.5.18) that motivated the override
is held by the pin alone.

The override was also strictly worse to carry, which is the real reason
to drop it. `update_package_json_overrides` merges framework overrides
into a project's `reflex.lock/package.json` and never removes entries, so
once an override ships it cannot be retired by a later release -- it
becomes a vestigial pin that only user intervention can clear, and one
that would actively conflict the next time the `postcss` dev pin moves
past the frozen override value.

`OVERRIDES` is kept as an empty mapping (the mechanism is still wired up
and unit-tested) with a note to prefer a dependency pin whenever the
package is one we declare directly.

Projects that already installed 0.9.8 keep an inert `"postcss": "8.5.23"`
override, confirmed by simulating that upgrade: the persisted entry
survives, the build succeeds, and postcss still resolves to 8.5.23. It
matches the dev pin, so it is a no-op until removed by hand.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZJDfyvTWhhJ25Lpch4VmS

* Scope the react-router v8 no-app-changes claim in the release note

The note opened with a blanket "No rxconfig.py or app code changes are
needed" and then said custom components wrapping react-router-dom must
change their imports, which contradicts it. Scope the claim to apps on
the default generated setup and state the react-router-dom import change
as required rather than as an aside.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ZJDfyvTWhhJ25Lpch4VmS

---------

Co-authored-by: Claude <noreply@anthropic.com>
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.

4 participants