moeru-ai/airi · error · Error
Vite did not expose a reachable dev server URL.
Error message
Vite did not expose a reachable dev server URL.
What it means
pickServerUrl reads ViteDevServer.resolvedUrls.network[0] falling back to local[0] and throws when both are absent — i.e. the running Vite server never exposed any reachable URL. cap-vite needs that URL to point the native app at the dev server.
Solutions
- Run a normal Vite dev server (not middleware mode) so resolvedUrls gets populated after listen()
- Only read the URL after the server is listening (await server.listen() or inside the post-listen hook)
- Fix underlying listen failures first (port in use + strictPort, bad server.host value)
Example fix
// before (middleware mode — resolvedUrls never populated)
const server = await createViteServer({ server: { middlewareMode: true }, plugins: [capVitePlugin({ capArgs: ['ios'] })] })
const url = pickServerUrl(server) // Error: Vite did not expose a reachable dev server URL.
// after
const server = await createViteServer({ server: { host: true }, plugins: [capVitePlugin({ capArgs: ['ios'] })] })
await server.listen()
const url = pickServerUrl(server) // resolvedUrls.local/network now set Defensive patterns
Strategy: validation
Validate before calling
// Only pick the URL once Vite actually resolved one
const urls = server.resolvedUrls?.network ?? server.resolvedUrls?.local
if (!urls?.length)
throw new Error('Dev server not listening yet — call this after server.listen()')
const url = pickServerUrl(server) Prevention
- Avoid middlewareMode when using cap-vite's plugin; run a normal dev server
- Read resolvedUrls only after listen() resolves
- Fix port-binding failures (strictPort + occupied port) before blaming the picker
When it happens
Trigger: Using the plugin with a Vite server created in middlewareMode (resolvedUrls stays null/empty), picking the URL before server.listen() completed, or a server config that prevents URL resolution (failed port binding with strictPort, unusual host config).
Common situations: Embedding cap-vite's plugin in a custom programmatic server setup instead of a normal `vite` dev run; racing the URL read against server startup; config that disables or alters server URL resolution.
Related errors
- CAP_VITE_CAP_ARGS_JSON must be a JSON string array.
- Chat Completions does not support file URLs
- Missing value for ` `.
- The first `cap run` argument must be `ios` or `android`.
- The first `cap run` argument must be `ios` or `android`.
AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18).
Data as JSON: /api/errors/cebefaa8c59ccb30.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cap-vite/src/native.ts:165
return capArgs
}
const targets = await listTargets(platform)
target = targets.find(device => device.id)?.id
}
if (!target) {
throw new Error(`No ${platform} devices or simulators found. Connect a device, start a simulator or emulator, or pass --target explicitly.`)
}
return [platformArg, '--target', target, ...rest]
}
export function pickServerUrl(server: Pick<ViteDevServer, 'resolvedUrls'>): URL {
const url = server.resolvedUrls?.network?.[0] ?? server.resolvedUrls?.local?.[0]
if (!url) {
throw new Error('Vite did not expose a reachable dev server URL.')
}
return new URL(url)
}
export function shouldRestartForNativeChange(file: string, platform: CapacitorPlatform, cwd: string): boolean {
const absoluteFile = resolve(cwd, file)
const platformRoot = resolve(cwd, platform)
if (!absoluteFile.startsWith(`${platformRoot}${sep}`) && absoluteFile !== platformRoot) {
return false
}
const fileName = basename(absoluteFile)
if (ignoredNames.has(fileName)) {
return false
}View on GitHub (pinned to 677329427f)