tauri-nuxt-dev
tauri-nuxt-dev
One command to run Tauri + Nuxt in development, with automatic free-port resolution.
Replaces the usual copy-pasted scripts/dev.ts: pick a free port, start Nuxt there, point Tauri at the same URL.
pnpm add -D tauri-nuxt-dev @tauri-apps/cli
# package.json → "dev": "tauri-nuxt-dev"
pnpm dev
Contents
- Why this exists
- Features
- Install
- Quick start
- Configuration (c12)
- CLI
- Recipes
- How it works
- Programmatic API
- Environment variables
- Security
- Migrating from
scripts/dev.ts - Related
- Development
- License
Why this exists
Tauri’s dev flow needs both sides to agree on a URL:
- Nuxt binds a free port.
- Tauri loads that exact
devUrl. beforeDevCommandstarts Nuxt on the same port.
If 3000 is busy, a naive script either crashes or leaves Tauri pointing at the wrong port. This library resolves a free port once, then builds a Tauri --config override so both stay in sync.
preferred port (config / NUXT_PORT / --port / 3000)
│
▼
get-port-please ──► free port
│
├─► beforeDevCommand (package-manager aware `nuxt:dev`)
└─► build.devUrl (http://localhost:<port>)
│
▼
tauri dev --config <json>
Your on-disk src-tauri/tauri.conf.json is never modified.
Features
- Auto free port via
get-port-please - c12 project config —
tauri-nuxt-dev.config.tsorpackage.json#tauri-nuxt-dev - Package-manager aware — pnpm / npm / yarn / bun
nuxt:devscripts - Local Tauri CLI first — resolves
@tauri-apps/cli, falls back toPATH - CLI + library —
tauri-nuxt-devbin, ortauriNuxtDev()in a script - Safe spawn — no
shell: true; shell-quotedbeforeDevCommandonly
Install
# pnpm
pnpm add -D tauri-nuxt-dev @tauri-apps/cli
# npm
npm i -D tauri-nuxt-dev @tauri-apps/cli
# yarn
yarn add -D tauri-nuxt-dev @tauri-apps/cli
# bun
bun add -d tauri-nuxt-dev @tauri-apps/cli
Requirements
| Node.js | ^20.19 / ^22.12 / >=24 (ESM only) |
| Frontend | Nuxt app with a nuxt:dev or dev script (or a local nuxt binary) |
| Tauri | @tauri-apps/cli locally (preferred), or tauri on PATH |
Quick start
1. Add the script
{
"scripts": {
"nuxt:dev": "nuxt dev",
"dev": "tauri-nuxt-dev"
}
}
2. Run
pnpm dev
If 3000 is free you get http://localhost:3000. If not:
Port 3000 is busy, using port 3001
3. Optional config
// tauri-nuxt-dev.config.ts
import { defineConfig } from 'tauri-nuxt-dev';
export default defineConfig({
port: 3000,
nuxt: 'vpr', // or omit to auto-detect `nuxt:dev`
});
Programmatic (same runner)
import { tauriNuxtDev } from 'tauri-nuxt-dev';
process.exitCode = await tauriNuxtDev({ port: 3000 });
Configuration (c12)
Project settings load with c12 — same pattern as genbumppush.
Where config lives
| Source | Example |
|---|---|
| Config file | tauri-nuxt-dev.config.ts / .js / .mjs / .cjs / .json |
package.json |
"tauri-nuxt-dev": { … } |
| Explicit path | --config ./custom.config.ts |
Discovery is rooted at cwd (--cwd / options.cwd).
TypeScript configs:
.tsconfig files load through native ESM when possible, then fall back to the optional peerjiti. Install it if your runtime cannot import the file directly:pnpm add -D jitiJSON and
package.json#tauri-nuxt-devconfigs need no extra dependency.
Resolution order (later wins)
- Built-in
defaults(empty; ports still honorNUXT_PORT/PORT) "tauri-nuxt-dev"key inpackage.json- C12 config file (or the
configFileyou pass) - CLI flags / programmatic options (highest priority)
loadTauriNuxtDevConfig also loads .env via setupDotenv({ cwd }) without overwriting existing env vars. Secrets belong in the environment — not in config files.
TauriNuxtDevConfig fields
| Field | Type | Default | Description |
|---|---|---|---|
port |
number |
NUXT_PORT / PORT / 3000 |
Preferred port |
portRange |
[from, to] |
[port, port + 99] |
Inclusive fallback scan |
host |
string |
unset / HOST |
Port checks, Nuxt --host, devUrl |
nuxt |
string | string[] |
auto | Nuxt starter binary or full argv |
tauriArgs |
string[] |
[] |
Extra args for tauri dev |
Runtime-only options on tauriNuxtDev (cwd, configFile, env, logger, signal) are never read from a config file.
defineConfig
import { defineConfig } from 'tauri-nuxt-dev';
export default defineConfig({
port: 3000,
portRange: [3000, 3100],
host: '0.0.0.0',
nuxt: 'vpr',
tauriArgs: ['--features', 'debug'],
});
Same object in package.json:
{
"tauri-nuxt-dev": {
"port": 3000,
"nuxt": "vpr",
"tauriArgs": ["--features", "debug"]
}
}
The return type is the package’s own TauriNuxtDevConfig — not c12 internals — so consumer declaration emit stays portable under pnpm (see genbumppush#6).
loadTauriNuxtDevConfig
import { loadTauriNuxtDevConfig } from 'tauri-nuxt-dev';
const config = await loadTauriNuxtDevConfig(process.cwd());
const forced = await loadTauriNuxtDevConfig(process.cwd(), './custom.config.ts', {
port: 4000,
});
CLI
tauri-nuxt-dev [options] [-- <tauri dev args>]
| Flag | Description |
|---|---|
--config <file> |
Explicit C12 config file |
--port <n> |
Preferred port |
--host <host> |
Port check + Nuxt --host + devUrl host |
--nuxt <bin> |
Nuxt starter binary (default: package-manager aware) |
--cwd <dir> |
Config discovery + spawn directory |
-h, --help |
Help |
-v, --version |
Version |
- Unrecognized flags and everything after
--go totauri dev. - CLI flags always win over the config file.
tauri-nuxt-dev
tauri-nuxt-dev --port 4000
tauri-nuxt-dev --config ./tauri-nuxt-dev.config.ts
tauri-nuxt-dev --host 0.0.0.0
tauri-nuxt-dev --nuxt vpr
tauri-nuxt-dev -- --features debug
tauri-nuxt-dev --port 4000 -- --target aarch64-apple-darwin
Recipes
Vite+ / vpr template
// tauri-nuxt-dev.config.ts
import { defineConfig } from 'tauri-nuxt-dev';
export default defineConfig({ nuxt: 'vpr' });
or
tauri-nuxt-dev --nuxt vpr
await tauriNuxtDev({ nuxt: ['vpr', 'nuxt:dev'] });
Device / mobile testing (bind all interfaces)
tauri-nuxt-dev --host 0.0.0.0
Nuxt gets --host 0.0.0.0. Tauri’s devUrl still uses localhost so the webview can connect (0.0.0.0 / :: are mapped automatically).
Force a port or range
import { defineConfig } from 'tauri-nuxt-dev';
export default defineConfig({
port: 4000,
portRange: [4000, 4100],
});
Programmatic runner with abort + custom logger
import { tauriNuxtDev } from 'tauri-nuxt-dev';
const code = await tauriNuxtDev({
cwd: process.cwd(),
configFile: './tauri-nuxt-dev.config.ts',
port: 3000,
host: '0.0.0.0',
nuxt: 'vpr',
tauriArgs: ['--features', 'debug'],
env: { FOO: 'bar' },
logger: {
info: (m) => console.info(m),
warn: (m) => console.warn(m),
error: (m) => console.error(m),
},
signal: AbortSignal.timeout(60_000),
});
process.exitCode = code;
How it works
- Load c12 config (file /
package.json+ CLI/API overrides). - Resolve a free port with
get-port-please
preferred → fallback range[port, port+99]. - Build the Nuxt command (package-manager aware — see below).
- Build a one-off Tauri config override:
{ "build": { "devUrl": "http://localhost:<port>", "beforeDevCommand": "<nuxt command> --port <port>" } } - Spawn
tauri dev --config <json>withshell: false. - Forward signals (
SIGINT/SIGTERM/AbortSignal) and resolve with the child exit code (0on clean signal exit).
# equivalent shape of what the runner passes to Tauri
tauri dev --config '{"build":{"devUrl":"http://localhost:3001","beforeDevCommand":"pnpm run nuxt:dev -- --port 3001"}}'
Package-manager aware Nuxt command
With "nuxt:dev": "nuxt dev" in package.json:
| PM | beforeDevCommand |
|---|---|
| pnpm | pnpm run nuxt:dev -- --port <port> |
| npm | npm run nuxt:dev -- --port <port> |
| yarn | yarn run nuxt:dev --port <port> |
| bun | bun run nuxt:dev --port <port> |
Fallbacks when there is no nuxt:dev script:
- Local
nuxtpackage →node <nuxt-bin> dev … nuxt devonPATH
Package-manager detection order: packageManager field → lockfile (pnpm-lock.yaml, yarn.lock, bun.lockb/bun.lock, package-lock.json) → npm_config_user_agent → npm.
Programmatic API
import {
// runner + config
tauriNuxtDev,
defineConfig,
loadTauriNuxtDevConfig,
toConfigOverrides,
defaults,
// ports
resolveDevPort,
parsePort,
// Tauri config payload
buildTauriDevConfig,
serializeTauriDevConfig,
toBrowserHost,
// Nuxt command
resolveNuxtDevCommandArgs,
formatShellCommand,
shellQuote,
// resolution helpers
detectPackageManager,
resolveLocalNuxtBin,
resolveTauriCli,
} from 'tauri-nuxt-dev';
tauriNuxtDev(options?): Promise<number>
End-to-end: load config → free port → spawn tauri dev → return exit code.
| Option | Type | Default | Notes |
|---|---|---|---|
port |
number |
env / 3000 |
Overrides config file |
portRange |
[from, to] |
[port, port+99] |
|
host |
string |
— | Port check + Nuxt + devUrl |
nuxt |
string | string[] |
auto | Binary or full argv |
tauriArgs |
string[] |
[] |
Forwarded to tauri dev |
cwd |
string |
process.cwd() |
Runtime-only |
configFile |
string |
auto | Runtime-only |
env |
NodeJS.ProcessEnv |
process.env |
Runtime-only |
logger |
Logger |
consola |
Runtime-only |
signal |
AbortSignal |
— | Runtime-only |
interface Logger {
info: (message: string) => void;
warn: (message: string) => void;
error: (message: string) => void;
}
Ports
import { resolveDevPort, parsePort } from 'tauri-nuxt-dev';
const port = await resolveDevPort(
{ port: 3000, portRange: [3000, 3100], host: '127.0.0.1' },
process.env,
);
parsePort('3000'); // 3000
parsePort('0'); // undefined
parsePort('abc'); // undefined
resolveDevPort option |
Default |
|---|---|
port |
NUXT_PORT / PORT / 3000 |
portRange |
[port, port+99] |
host |
HOST env |
name |
'tauri-nuxt-dev' |
parsePort accepts a string or number and returns undefined outside 1..65535.
Tauri config payload
import { buildTauriDevConfig, serializeTauriDevConfig, toBrowserHost } from 'tauri-nuxt-dev';
const config = buildTauriDevConfig({
port: 3000,
host: '0.0.0.0',
beforeDevCommand: 'pnpm run nuxt:dev -- --port 3000 --host 0.0.0.0',
});
// { build: { devUrl: 'http://localhost:3000', beforeDevCommand: '…' } }
serializeTauriDevConfig(config); // JSON string for `--config`
toBrowserHost('::1'); // '[::1]'
toBrowserHost('0.0.0.0'); // 'localhost'
Nuxt command helpers
import { resolveNuxtDevCommandArgs, formatShellCommand, shellQuote } from 'tauri-nuxt-dev';
const args = resolveNuxtDevCommandArgs({
cwd: process.cwd(),
port: 4000,
host: '0.0.0.0',
});
// ['pnpm', 'run', 'nuxt:dev', '--', '--port', '4000', '--host', '0.0.0.0']
formatShellCommand(args);
// 'pnpm run nuxt:dev -- --port 4000 --host 0.0.0.0'
shellQuote('hello world'); // "'hello world'" (POSIX)
shellQuote('hello world', 'win32'); // '"hello world"' (cmd.exe)
Resolution helpers
import { detectPackageManager, resolveLocalNuxtBin, resolveTauriCli } from 'tauri-nuxt-dev';
detectPackageManager('/path/to/app');
// { name: 'pnpm', packageJsonPath: '…/package.json', scripts: { … } }
resolveLocalNuxtBin();
// absolute path to nuxt bin, or undefined
resolveTauriCli();
// local: { command: process.execPath, prefixArgs: [entry], source: 'local' }
// path: { command: 'tauri', prefixArgs: [], source: 'path' }
Local Tauri CLI is spawned as node <resolved-entry> … (no shell, cross-platform).
Environment variables
| Variable | Role |
|---|---|
NUXT_PORT |
Preferred port after config/--port |
PORT |
Fallback preferred port |
HOST |
Default host for port checks |
npm_config_user_agent |
Last-resort package-manager hint |
Security
- Child processes use
shell: false. - Local Tauri CLI runs via
node <resolved-entry>, not a shell string. - Ports are validated as safe integers in
1..65535. devUrlhosts are validated; wildcard binds map tolocalhostand bare IPv6 is bracketed.beforeDevCommandis a shell string only because Tauri requires one; argv tokens are shell-quoted for the platform (sh/POSIX single quotes on Unix,cmd.exedouble quotes on Windows).- Unknown CLI args are argv passthrough to
tauri dev, not shell interpolation. .envis loaded without overwriting existing environment variables.
Report vulnerabilities via GitHub Security Advisories.
Migrating from scripts/dev.ts
Before — hand-rolled port scan + spawn in a template script.
After — config + one line:
// tauri-nuxt-dev.config.ts
import { defineConfig } from 'tauri-nuxt-dev';
export default defineConfig({ nuxt: 'vpr' });
{
"scripts": {
"dev": "tauri-nuxt-dev"
}
}
Or keep a thin script:
// scripts/dev.ts
import { tauriNuxtDev } from 'tauri-nuxt-dev';
process.exitCode = await tauriNuxtDev({ nuxt: 'vpr' });
Related
- create-tauri-nuxt — Tauri + Nuxt template this library was extracted from
- get-port-please — port resolution
- c12 — config loading
- Tauri CLI —
tauri dev - Nuxt —
nuxt dev
Development
pnpm install
pnpm test
pnpm build
pnpm check
| Script | Description |
|---|---|
pnpm build |
tsdown build (+ publint) |
pnpm test |
Vitest unit tests |
pnpm typecheck |
TypeScript check |
pnpm lint / pnpm check |
oxlint / format + lint gate |
pnpm release |
Version bump + publish (genbumppush) |
Optional peer for TypeScript config files: jiti (see Configuration).
License
Related Packages
Version History
| Version | Released | License |
|---|---|---|
| 0.0.1 | MIT | |
| 0.0.0 |