concile
Deploy & Operate

Vite plugin

Run your frontend and the concile backend with one vite command, on one origin.

Usually, working with a Vite app and a concile backend means juggling two separate terminals: one for vite and another for concile dev. You also have to set up a custom proxy just so your browser can talk to both. But with @concile/vite, you can collapse all of that into a single handy command! Just run vite, and our plugin automatically fires up the backend right alongside it, all on the same browser origin.

Having everything on one origin is actually a bigger deal than just saving you a terminal window. Since the client connects to ${location.host}/api/sync, running the engine on the exact same host and port as your frontend means you can completely skip writing proxy configurations and forget about tricky CORS issues. It really just works right out of the box!

You'll find two helpful modes available through a simple concile() call:

  • Proxy mode (the default option) spins up concile dev as a child process and automatically hooks up Vite's proxy to it. Your backend runs exactly as it would if you started it manually.
  • Embed mode actually boots up the engine right inside Vite's own process. Because there is no child process and no proxy hop, the engine acts as seamless Vite middleware, and the reactive WebSocket easily shares Vite's HTTP server.

Set up

Install the plugin

@concile/vite goes right next to your existing dev dependencies. It needs vite 5 or newer, and it drives @concile/cli (which is already a dev dependency if you followed the quickstart):

npm install -D @concile/vite

Add it to vite.config.ts

vite.config.ts
import { defineConfig } from "vite";
import { concile } from "@concile/vite";

export default defineConfig({
  plugins: [concile()],
});

That's the entire configuration. You do not need to add any options.

Run vite

npx vite

The backend starts up right along with the dev server. You will see its log lines appear in the same terminal, prefixed with [concile]. Open your app at the Vite URL, and you'll find the engine's surfaces are there too, all on the very same origin:

  • /api/* is the engine's HTTP API, and /api/sync is the reactive WebSocket.
  • /_dashboard is the dashboard.
  • /_admin/* is the admin API the dashboard runs on.

Everything else is your Vite app, totally untouched.

The two modes

Proxy mode spawns concile dev as a child process on a free port and injects three entries right into Vite's dev-server proxy: /api (which is WebSocket-aware, so /api/sync upgrades pass through seamlessly), /_dashboard, and /_admin. Vite merges these with any server.proxy entries of your own, ensuring your unrelated proxy rules are kept perfectly intact.

Since the child process is the real concile dev, everything on the local development page works exactly as described. Codegen runs on start, the child watches the concile/ directory and hot-reloads your functions on every save, and the ephemeral admin key is printed in its piped output.

Here are a few handy mechanics you should definitely keep in mind:

  • CLI resolution. The plugin runs your app's local node_modules/.bin/concile if it exists, and falls back to npx concile otherwise. You can override this behavior with the command option.
  • Readiness. Vite patiently waits for the backend to accept TCP connections before serving. It polls every 200ms with a 30-second timeout. If the child process exits early or never comes up, vite fails with a clear error instead of serving a dead proxy.
  • Cleanup. The child process gets killed when the Vite server closes and on SIGINT, SIGTERM, or process exit. This means a stopped vite will never leave an orphaned backend behind.
  • Extra flags. Anything that concile dev accepts can be easily forwarded through args. For example, you can pass args: ["--database-url", "postgres://..."].

Options

All options are completely optional. mode and functionsDir apply to both modes, while the rest are specific to one mode and simply ignored by the other.

Prop

Type

Hot reload and HMR

Two independent reload loops run side by side, and neither steps on the toes of the other:

  • Frontend HMR is Vite's, completely unchanged. Editing a component hot-swaps it in the browser exactly as you would expect in any Vite app.
  • Backend hot reload is concile's. Saving a file under concile/ reloads your schema and functions and regenerates concile/_generated/, without ever dropping your live subscriptions. A client watching a query stays happily connected and re-runs against the new code on the very next invalidation. A broken save simply logs ✗ reload failed and keeps your previous working functions running in the meantime.

In proxy mode, the child process's own file watcher handles this, exactly as described under local development. In embed mode, the plugin subscribes to Vite's own file watcher instead. Any changes under functionsDir (except for _generated/) are debounced for 50ms and then re-pushed in-process.

Regenerated _generated/ files are treated as ordinary source files as far as Vite is concerned. This means a codegen change that your frontend imports, like a new function on api, flows right into the browser through Vite's normal HMR system.

Limits

  • The plugin is strictly for development. It only shapes vite's dev server. Running vite build produces your frontend just like always, and you will deploy the backend separately (self-hosting, deploy and build).
  • Embed mode caps request bodies at 5 MiB on the engine paths. A larger file storage upload through /api/storage/upload won't fit here. You should use proxy mode or an S3-backed presigned upload for big files.
  • Embed mode needs Vite's own HTTP server. If you use server.middlewareMode (which is how some SSR frameworks host Vite), there is no server to attach to. This means the /api/sync WebSocket never wires up and engine cleanup doesn't run. The plugin will warn you loudly in this case, so stick to proxy mode there.
  • Proxy mode is two processes. This is completely by design to ensure zero divergence from the real concile dev, but it means backend state lives in the child process. If you ever need to poke the engine directly from Vite's process, that's exactly what embed mode is for.
  • Local development: everything the backend does under the plugin, including flags, hot reload, and the dashboard.
  • Quickstart: install concile and see the whole loop run end to end.

On this page