All but React Native are in beta.
All but React Native are in beta.

Beta. Buoy runs in Vue 3.3 and up, built with Vite. It runs in Nuxt 4 too. Buoy brings its own React for its own screens. Your app stays all Vue.
Let your coding agent install Buoy:
To do it by hand, use these four steps. For Nuxt, skip to Nuxt.
Run this in your app folder:
npm install --save-dev @buoy-gg/core @buoy-gg/vue @buoy-gg/networknpm install --save-dev @buoy-gg/core @buoy-gg/vue @buoy-gg/network
npm install --save-dev @buoy-gg/core @buoy-gg/vue @buoy-gg/network
Add more tools the same way, such as @buoy-gg/storage.
Keep all Buoy packages on the same release.
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { buoy } from '@buoy-gg/core/vite';
export default defineConfig({
plugins: [vue(), buoy()],
});
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { buoy } from '@buoy-gg/core/vite';
export default defineConfig({
plugins: [vue(), buoy()],
});
The plugin finds the tools you installed.
It adds Buoy's early hook to index.html.
It leaves Buoy out of prod builds.
// src/main.ts
import { createApp } from 'vue';
import { buoy } from '@buoy-gg/vue';
createApp(App)
.use(pinia)
.use(router)
.use(VueQueryPlugin, { queryClient })
.use(buoy)
.mount('#app');
// src/main.ts
import { createApp } from 'vue';
import { buoy } from '@buoy-gg/vue';
createApp(App)
.use(pinia)
.use(router)
.use(VueQueryPlugin, { queryClient })
.use(buoy)
.mount('#app');
It can go anywhere in the use chain.
When your app mounts, it finds your router, Pinia and Vue Query client.
It starts Buoy in dev builds.
It stops Buoy when your app unmounts.
It adds nothing to your page.
npx buoy login
npx buoy login
Sign in with a Free or Pro account.
The CLI writes VITE_BUOY_KEY to a file that Git ignores.
The Buoy plugin reads it for you, so you add no key to your code.
Run your app with npm run dev.
Open Buoy's dial, then open Network.
Make a web call in your app and check the list.
| Tool | In Vue |
|---|---|
| Network, Storage, Console | Yes. They watch the browser itself. |
| Routes | Yes. It shows route names and params from Vue Router. |
| Query | Yes, with Vue Query. See below. |
| Zustand | Yes, for Pinia stores, Vue refs and vanilla Zustand stores. |
| Highlight | Yes. It boxes the components that rendered. |
| Redux | Yes, for Redux Toolkit. Load @buoy-gg/redux/web before you make the store. |
| Jotai | Yes, for jotai/vanilla atoms. Pass them to watchAtoms. |
| Clock, Lifecycle, Location, Permissions | Yes. They work in a plain browser. |
| Env, Events, Perf Monitor, Images, Impersonate, Time Machine | Yes. |
Routes shows each page's name and params.
Each page change shows up once, the first page too.
The name is the route's name:
{ path: '/pokemon/:id', name: 'Pokémon', component: Detail }
{ path: '/pokemon/:id', name: 'Pokémon', component: Detail }
A route with no name shows its path.
The sitemap lists every route your router has.
It updates when you add or remove routes.
The Go button and the MCP navigate tool use Vue Router.
Install the Query tool and React Query too:
npm install --save-dev @buoy-gg/react-query @tanstack/react-query
npm install --save-dev @buoy-gg/react-query @tanstack/react-query
Use the same version as your @tanstack/vue-query.
Both must share one copy of @tanstack/query-core.
The Buoy plugin finds your client from VueQueryPlugin.
A custom queryClientKey works too.
Your Pinia stores show up in Buoy's Zustand tool by themselves. Each one goes by its id. A store shows up the first time your app uses it. You can read and edit a store in the tool. An edit keeps the fields you did not change. Getters and actions are not shown.
To show a plain ref or reactive, pass it to the tool:
import { buoyVueRef } from '@buoy-gg/vue/pinia';
app.use(buoy, {
zustandStores: { count: buoyVueRef(count) },
});
import { buoyVueRef } from '@buoy-gg/vue/pinia';
app.use(buoy, {
zustandStores: { count: buoyVueRef(count) },
});
A ref that holds one value shows up as { value }.
Vanilla Zustand stores go in the same list as they are.
Highlight boxes the components that rendered.
It names each one by its component name.
It skips Vue's own Transition, KeepAlive, Suspense and Teleport.
It reads Vue's dev hook, so it works in dev builds.
It works next to Vue DevTools.
That's the browser extension, vite-plugin-vue-devtools and Nuxt DevTools.
Buoy shares Vue's dev hook with them, in any order.
Vue 3.6's Vapor components get no boxes yet. Your other components still do.
Put app settings for Buoy in their own file.
Load it with load, behind a dev check:
app.use(buoy, {
load: import.meta.env.DEV
? () => import('./buoy-settings').then((m) => m.settings)
: undefined,
});
app.use(buoy, {
load: import.meta.env.DEV
? () => import('./buoy-settings').then((m) => m.settings)
: undefined,
});
The dev check lets the prod build drop that file.
To keep Buoy in a QA build, set both of these:
buoy({ enableInRelease: true })
buoy({ enableInRelease: true })
app.use(buoy, { enableInRelease: true })
app.use(buoy, { enableInRelease: true })
The Vite plugin also keeps Vue's dev hook in that build, so Highlight still works. Account and plan rules still apply.
Install Buoy the same way, then add the module:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@buoy-gg/vue/nuxt'],
});
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@buoy-gg/vue/nuxt'],
});
That's all.
You need no Vite plugin and no app.use.
The module adds Buoy's early hook to Nuxt's browser entry.
It starts Buoy in the browser, after Nuxt makes your app.
The server build gets none of Buoy.
Neither does a prod build.
Pass options with the buoy key:
export default defineNuxtConfig({
modules: ['@buoy-gg/vue/nuxt'],
buoy: { options: { environment: 'qa' } },
});
export default defineNuxtConfig({
modules: ['@buoy-gg/vue/nuxt'],
buoy: { options: { environment: 'qa' } },
});
These options must be plain data.
For options with functions, such as load, set setup: false.
Then start Buoy in your own client plugin:
// nuxt.config.ts
buoy: { setup: false },
// nuxt.config.ts
buoy: { setup: false },
// app/plugins/buoy.client.ts
import { buoy } from '@buoy-gg/vue';
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(buoy, { load });
});
// app/plugins/buoy.client.ts
import { buoy } from '@buoy-gg/vue';
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(buoy, { load });
});
@pinia/nuxt stores show up by themselves.
Route names come from your page names.
Nuxt names a page after its file, such as index.
Network shows the calls your app makes in the browser.
A useFetch that runs on the server is not in the list.
The browser gets its answer inside the page.
For a QA build, set buoy: { enableInRelease: true }.
The Buoy plugin still runs, but it loads no tools by itself.
Pass them with modules.
Put this line first in your entry file, before createApp:
import '@buoy-gg/vue/register';
import '@buoy-gg/vue/register';
Your build must also drop Buoy from prod.
Resolve the buoy-disabled export condition there.
The types in @buoy-gg/vue need no React types.
Code that imports @buoy-gg/core or a tool's /web entry is different.
Those types name React types your app does not have.
Vue's own starter sets skipLibCheck: true, so it type-checks as it is.
If yours doesn't, set it in tsconfig.json.