Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 by adding `rx.plugins.FrontendInspectorPlugin()` to `plugins` 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
90 changes: 90 additions & 0 deletions docs/advanced_onboarding/frontend_inspector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
```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. The plugin is a no-op under `REFLEX_ENV_MODE=prod`, so the same `rxconfig.py` works for both dev and prod runs.

## Enable

Add `FrontendInspectorPlugin` to `plugins` in your `rxconfig.py`:

```python
import reflex as rx

config = rx.Config(
app_name="my_app",
plugins=[rx.plugins.FrontendInspectorPlugin()],
)
```

Run your app in dev mode (`uv run reflex run`). The inspector loads automatically; the `launch-editor` dev dependency is 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",
plugins=[
rx.plugins.FrontendInspectorPlugin(
# Custom shortcut. Modifier aliases like cmd / option are accepted.
shortcut="ctrl+shift+i",
# Optional: override the editor invocation. Empty falls back to
# $REFLEX_EDITOR / $VISUAL / $EDITOR / launch-editor's auto-detection.
editor="code -g",
),
],
)
```

| Argument | Default | Notes |
| --- | --- | --- |
| `shortcut` | `"alt+x"` | Modifiers: `alt`, `ctrl`, `meta` (`cmd`/`super`/`win`), `shift`. |
| `editor` | `""` | Forwarded to [`launch-editor`](https://github.com/yyx990803/launch-editor). |

## Production safety

The plugin's hooks all return empty when `REFLEX_ENV_MODE=prod`, so prod builds emit no inspector wiring:

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

The gate is re-evaluated at every emission site, so the same `rxconfig.py` works in both dev and prod without further changes.

## 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 Vite plugin is registered with `apply: 'serve'`, 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 @@ -30,6 +30,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 @@ -12,6 +12,7 @@
plugins=[
rx.plugins.TailwindV4Plugin(),
rx.plugins.SitemapPlugin(trailing_slash="always"),
rx.plugins.FrontendInspectorPlugin(),
AgentFilesPlugin(),
],
)
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