Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/advanced_onboarding/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ uv run reflex run --frontend-port 3001

See the [CLI reference](/docs/api-reference/cli) for all the arguments available.

## Frontend Inspector

For mapping rendered DOM nodes back to the Python source that produced them, see the [Frontend Inspector](/docs/advanced_onboarding/frontend_inspector) page. Enable it in dev with `frontend_inspector="dev"` in your `rxconfig.py`.

## Customizable App Data Directory

The `REFLEX_DIR` environment variable can be set, which allows users to set the location where Reflex writes helper tools like Bun and NodeJS.
Expand Down
100 changes: 100 additions & 0 deletions docs/advanced_onboarding/frontend_inspector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
```python exec
import reflex as rx
```

# Frontend Inspector

The frontend inspector maps rendered DOM nodes back to the Python source line that created them. Hover an element in the browser, see which `Component.create(...)` call produced it, and click to open that line in your editor.

It is a development-only tool. Enabling it in a production build is a configuration error.

## Enable

Set `frontend_inspector="dev"` in your `rxconfig.py`:

```python
import reflex as rx

config = rx.Config(
app_name="my_app",
frontend_inspector="dev",
)
```

Run your app in dev mode (`uv run reflex run`). The inspector loads automatically; the `launch-editor` package is added to `.web/package.json` and installed during the same compile pass.

## Usage

Three modes:

- **Hover with `alt` held** — show the overlay while inspecting. The overlay disappears as soon as you release `alt`.
- **`alt+x`** — toggle persistent mode. The overlay stays on; the small `rx-inspect` button in the bottom-right corner reflects the state.
- **`alt`+click** — open the source file at the captured line in your editor. The modifier is required at click time so re-focusing the browser window doesn't hijack normal clicks.

`Esc` exits persistent mode. Pressing `c` while hovering copies `path:line:column` to the clipboard.

## Configuration

```python
config = rx.Config(
app_name="my_app",
frontend_inspector="dev",
# Custom shortcut. Modifier aliases like cmd / option are accepted.
frontend_inspector_shortcut="ctrl+shift+i",
# Optional: override the editor invocation. Empty falls back to
# $REFLEX_EDITOR / $VISUAL / $EDITOR / launch-editor's auto-detection.
frontend_inspector_editor="code -g",
)
```

| Field | Default | Notes |
| --- | --- | --- |
| `frontend_inspector` | `"off"` | `"off"` disables it (default), `"dev"` enables it in dev. Prod builds reject `"dev"`. |
| `frontend_inspector_shortcut` | `"alt+x"` | Modifiers: `alt`, `ctrl`, `meta` (`cmd`/`super`/`win`), `shift`. |
| `frontend_inspector_editor` | `""` | Forwarded to [`launch-editor`](https://github.com/yyx990803/launch-editor). |

## Personal preferences via environment variables

The shortcut and editor invocation are personal; you usually do not want to commit them to a shared `rxconfig.py`. Reflex reads the matching env vars at config time:

```bash
REFLEX_FRONTEND_INSPECTOR=dev
REFLEX_FRONTEND_INSPECTOR_SHORTCUT=ctrl+shift+i
REFLEX_FRONTEND_INSPECTOR_EDITOR=cursor
```

Set them in your shell, point Reflex at a dotenv file with `REFLEX_ENV_FILE=.env`, or pass `env_file=".env"` to `rx.Config(...)`. Reflex does not auto-discover a `.env` in the project root.

## Production safety

`frontend_inspector="dev"` raises `ConfigError` whenever `REFLEX_ENV_MODE=prod`, including:

- `uv run reflex run --env prod`
- `uv run reflex export --env prod`
- Any deploy that sets `REFLEX_ENV_MODE=prod`.

The check runs at compile time after the env mode is settled, so the safety net works even when the env is set on the command line.

## What it does and does not do

It does:

- Add a small `data-rx="<id>"` attribute to every component that has a non-Fragment tag.
- Emit `.web/public/__reflex/source-map.json` mapping ids to `(file, line, column, component)`.
- Mount a Vite dev-server middleware at `/__open-in-editor` that calls `launch-editor`.

It does not:

- Inspect React state or props at runtime — it is a source-mapping tool, not a React DevTools replacement.
- Run in production. The plugin is registered with `apply: 'serve'` in Vite, so even if a stray asset slipped through, prod builds would not load it.
- Modify your source code. The inspector stores a private id on each component that gets rendered out as a `data-rx` attribute; your `rxconfig.py` and component files are untouched.

## Programmatic toggle

When the inspector is loaded, `window.__REFLEX_INSPECTOR__` exposes the runtime API for ad-hoc debugging in the browser console:

```js
window.__REFLEX_INSPECTOR__.enable();
window.__REFLEX_INSPECTOR__.toggle();
window.__REFLEX_INSPECTOR__.sourceCount(); // number of mapped ids
```
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def get_sidebar_items_learn():
advanced_onboarding.how_reflex_works,
advanced_onboarding.configuration,
advanced_onboarding.code_structure,
advanced_onboarding.frontend_inspector,
],
),
]
Expand Down
1 change: 1 addition & 0 deletions docs/app/rxconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
frontend_packages=[
"tailwindcss-animated",
],
frontend_inspector="dev",
telemetry_enabled=False,
plugins=[
rx.plugins.TailwindV4Plugin(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* Dev-server middleware that opens a file:line:column in the user's editor.
*
* Mounted by the inspector Vite plugin (and the equivalent Astro hook).
* The browser sends `GET /__open-in-editor?file=<path>:<line>:<col>`.
*/

import launch from "launch-editor";

const reflexEditorMiddleware = (req, res, next) => {
if (!req.url || !req.url.includes("/__open-in-editor")) {
return next();
}
let url;
try {
url = new URL(req.url, "http://localhost");
} catch (err) {
res.statusCode = 400;
res.end("Invalid URL");
return;
}
const file = url.searchParams.get("file");
if (!file) {
res.statusCode = 400;
res.end("Missing 'file' query parameter");
return;
}
const editor = process.env.REFLEX_EDITOR || undefined;
launch(file, editor, (fileName, errorMsg) => {
if (errorMsg) {
// eslint-disable-next-line no-console
console.error("[reflex-inspector] launch-editor:", errorMsg);
}
});
res.statusCode = 204;
res.end();
};

export default reflexEditorMiddleware;
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* Reflex frontend inspector — dev-only overlay styles. */

#__reflex_inspector_outline {
position: fixed;
pointer-events: none;
z-index: 2147483646;
border: 2px solid #4f8cff;
background: rgba(79, 140, 255, 0.08);
border-radius: 2px;
transition: top 60ms linear, left 60ms linear, width 60ms linear, height 60ms linear;
display: none;
}

#__reflex_inspector_label {
position: fixed;
pointer-events: none;
z-index: 2147483647;
background: #111827;
color: #f9fafb;
font: 500 12px/1.4 "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
padding: 4px 8px;
border-radius: 4px;
white-space: nowrap;
display: none;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
}

#__reflex_inspector_label .__reflex_inspector_chain {
color: #9ca3af;
margin-right: 6px;
}

#__reflex_inspector_label .__reflex_inspector_file {
color: #93c5fd;
}

#__reflex_inspector_toggle {
position: fixed;
bottom: 16px;
right: 16px;
z-index: 2147483647;
background: #111827;
color: #f9fafb;
border: 1px solid #374151;
border-radius: 999px;
font: 500 12px/1 "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
padding: 8px 12px;
cursor: pointer;
opacity: 0.65;
transition: opacity 120ms ease;
}

#__reflex_inspector_toggle:hover {
opacity: 1;
}

#__reflex_inspector_toggle[data-active="true"] {
background: #4f8cff;
border-color: #4f8cff;
opacity: 1;
}
Loading
Loading