connect() gets into your app, and whether you get component identity on top.
Source pointer is the file:line on a verdict, the thing an agent follows straight to the code after a red. It comes from the build plugin stamping your JSX, not from the SDK, so it is a property of how the framework is wired rather than of the framework. Remix and Astro render their own HTML, so the plugin’s connect-injection cannot fire there, and both examples used to drop the plugin entirely, which quietly cost them the stamping too. Both now wire it with inject: false, which keeps the half that applies. A row marked unproven has no example app, so nobody has checked.
Where each framework stands
We separate “we tested this” from “this should work”. Four frameworks have an example app and a CI gate. The rest are wired and documented but nothing proves they still work. Two columns say different things and it is worth keeping them apart. CI gateyes means an example app is driven to a verdict on every change. install only means the install gate scaffolds that stack from scratch, runs init, boots it and waits for a session, so the setup is proven and the drive is not. Vue is there because a release once nearly shipped an installer that left a Vue app connecting 0% of the time, and the gate that exists for exactly that had only React scaffolds.
Source pointer is the file:line on a verdict. It needs the build plugin to stamp the source, which it does for JSX and, separately, for Svelte components. Vue single-file components are neither, so a Vue app gets every other signal and no source field. Measured, not assumed: a Svelte counter returns src/lib/Counter.svelte:5 and the same drive on Vue returns none.
init detects the UI library separately from the framework. That distinction exists because detection used to stop at “vite is in package.json”, which installed the React adapter into Vue and Preact apps and reported all green with nothing attached.
React on Vite
The shortest path, and the one with the most in it.connect() and stamps source locations, which is what turns a DOM node into src/components/Login.tsx:81.
Next.js
Keeps SWC. No Babel migration.@reticlehq/next is CommonJS on purpose. next.config.js is loaded by Node before any ESM
transform, so an ESM-only helper fails at the least helpful moment.Remix and Astro
Remix uses the Vite plugin withinject: false: Remix SSRs its own HTML, so the plugin’s connect-injection does not fire, but the stamping half still puts file:line on a verdict.
Astro is the same on the plugin (wire it with inject: false if you want source pointers on JSX islands) and uses a different channel for the pairing token. vite.define does not reach Astro’s client pipeline on 7.2+, so the identifier stays literal and the bridge refuses the dial. init reads the token in frontmatter and puts it on a <meta name="reticle-pairing-token">. Reading it per request also means a token written after the dev server started is picked up on the next load, with no restart.
Use a processed page <script> with a static SDK import. Vite owns that module graph from the first transform:
<script> tag must not sit inside a conditional. Astro hoists them statically, so {isDev && (<script />)} is never hoisted. init writes the same <meta> tags and a local src/components/ReticleDev.ts that the script statically imports. It also raises vite.build.target to es2022 (Astro’s default down-levels the SDK), declares the SDK in optimizeDeps.include, and keeps .reticle/ out of the watcher. Static .astro markup still gets DOM, network and routing without the React integration.
SvelteKit
SvelteKit renders throughapp.html and never triggers Vite’s index.html injection, so the plugin cannot auto-connect. A client hook is the reliable path, and it is what init writes.
data-reticle-source into .svelte components, so verdicts carry file:line.
For state, use the svelteStore adapter. See State management.
TanStack Start
TanStack Start SSRs<html> from src/routes/__root.tsx and never sends Vite’s index.html, so the plugin cannot auto-connect. Keep the plugin with inject: false: only the injection half is inapplicable, and the stamping half is what puts file:line on a verdict.
Connect from a client-only effect. A static import of the SDK on __root.tsx SSRs and 500s.
init detects Start and prints this recipe. There is no example app and no CI gate, so treat a connected session as the proof, not the plan.
Nuxt
Nuxt owns its own Vite instance and renders its own HTML. There is novite.config to patch and no index.html to inject into, so reticle init writes a dev-only client plugin and adds a vite.define block to nuxt.config. That block is the only place a Nuxt app can inline the pairing token, which the bridge requires even on localhost. What it writes is this, and the notes below are why each line is the way it is.
The .client suffix is load-bearing
The .client suffix is load-bearing
It is what keeps the plugin out of the server bundle. Without it you are running the SDK during
SSR, where
window does not exist.Guard on import.meta.dev, never on hostname
Guard on import.meta.dev, never on hostname
window.location.hostname === 'localhost' fails twice over here. window does not exist in
SSR, and the check is false on any hosts-file alias or LAN address. import.meta.dev resolves
at build time and does not care what host you develop on.Restart the dev server
Restart the dev server
A running dev server does not pick up a new plugin. It will not appear in
.nuxt/plugins/client.mjs, and the app comes up with no SDK at all and no error saying so.@reticlehq/browser, the framework-neutral sensor. You get DOM, network, console, routing and source file:line. What you do not get is React component identity, which is the only thing the React adapter adds.
Vue
Vue apps outside Nuxt are Vite apps, soinit finds them. Install the sensor rather than the React kit.
Preact
Detected as its own UI library, because a Preact app usingpreact/compat aliases React and would otherwise be handed the React kit with nothing to attach to.
The sensor works. If you use preact/compat, the React adapter can attach through the alias, but that path is not gated.
Create React App and plain bundlers
No config file to patch, soconnect() goes in your entry module.
Plain HTML
NODE_ENV=production, but a second lock on a door leading to your users is a reasonable number.
Server-rendered apps (Django, Flask, FastAPI, Rails)
There is no JS build to import the SDK from, and no single template to paste a script tag into: an app has many templates and may not have a base one. The SDK loads from a URL instead, so a page with no build step is not a blocker.reticle init detects these and prints the right thing for the stack it found, writing .reticle.json as it goes. What follows is the same wiring, written down so you can read it before running anything, or wire it by hand.
Django
Middleware is the Django-shaped answer: one place, runs for every page, no template to find.reticle init emits this with your real version, project id and pairing token filled in. Copy it from there rather than from this page: the placeholders above are the three things that have to be right.
Four details in that middleware are load-bearing, and each is there because the obvious version is wrong:
settings.DEBUGis the guard. Reticle is dev-only, and this is the lock that cannot be forgotten at deploy time.- the
text/htmlcheck keeps it from corrupting a JSON API response or a file download. - the
streamingcheck exists because aStreamingHttpResponsehas no.contentto rewrite, and touching it raises. </body>, not<head>so the SDK sees a parsed document.
Content-Length matters for the same reason: the body grew, and a stale length truncates the page in the browser rather than failing loudly.
Flask and FastAPI with Jinja
Same shape, one hook instead of a middleware class: an@app.after_request in Flask, or a middleware in FastAPI, with the same four guards: a debug flag, text/html only, not streaming, and injected before </body>.
Streamlit
Different again: Streamlit owns the top-level document, so there is no served template, and scripts passed tost.markdown do not run. Use st.html on Streamlit 1.63 or newer. reticle init prints the helper, which also guards against connecting twice across reruns.
What you get, and what you do not
Everything that comes from reading the live page works: queries, snapshots, actions, network and console channels, verdicts. What is missing is the source pointer.file:line on a verdict comes from the build plugin stamping data-reticle-source, and there is no build step here to stamp it. You get every other signal and no source field, the same way a Vue single-file component does.
Desktop is a different shape
Electron and Tauri invert the direction: your app dials the daemon, and there is no URL for an agent to open. Reticle also reaches the main-process and Rust IPC boundary, which the renderer cannot see.Desktop apps
Electron and Tauri, IPC observation, and the CSP trap whose failure is completely silent.
Not supported
Angular and Solid appear in dependency detection but have no wiring, no adapter and no gate. The sensor may work if you callconnect() yourself. Nothing proves it, so we are not going to list them as supported.
Frequently asked questions
Does Reticle work with Next.js?
Does Reticle work with Next.js?
Yes, and it keeps SWC. Install
@reticlehq/react and @reticlehq/next, then wrap your config
with withReticle. There is no Babel migration. Next.js has an example app and a CI gate, so it
is supported rather than merely wired, and you get component identity and file:line source
pointers.Does Reticle work with Vue?
Does Reticle work with Vue?
Partly, and the limit is specific. Install
@reticlehq/browser rather than the React kit, and you
get DOM, network, console, routing, actions and Pinia state. What you do not get is a source
field: source stamping covers JSX and Svelte components, and a Vue single-file component is
neither. The install path is covered by a gate; the drive is not.Which framework should I pick if I want the strongest verdicts?
Which framework should I pick if I want the strongest verdicts?
React on Vite. It is the shortest install, and it is the only combination that gives you all three
of component identity,
file:line source pointers and a CI-gated example app. If you are already
on Next.js, Remix or Astro, you get the same guarantees.My framework is not listed at all. Will it work?
My framework is not listed at all. Will it work?
Probably, with one line.
@reticlehq/browser is the framework-neutral sensor: call
reticle.connect() behind a dev guard anywhere your app boots, and you get DOM, network, console
and routing. What you lose is component identity and, unless a build plugin stamps your source,
the file:line pointer. If it does not register a session, that is a bug worth reporting.What does 'wired, unverified' actually mean for me?
What does 'wired, unverified' actually mean for me?
The recipe on this page was written from a real integration and is expected to work, but no app
in CI exercises it, so nothing will tell us when it breaks. If you depend on one of these, the
useful thing you can do is report it when it stops working, since that is the only signal there
is.
Something missing?
A framework you need is a
gap report. It is the signal that decides what gets built.