Hot reload
VextJS provides development reload through vext dev. It watches file changes and, according to their role, runs a backend soft reload, frontend rebuild, or application Worker cold restart.
First verify a response change after saving with the example below. Then review reload scope, state retention, and failure recovery. Reload is for development feedback; for production process updates, see Cluster.
Quick Start
1. Prepare an application and route
Use an existing TypeScript API application, or create one through the CLI walkthrough and install dependencies. Add this file at the project root:
The filename supplies the /reload-demo prefix automatically. Do not repeat it in the route's "/". The PID is only for observing process changes in this example.
2. Start and record a baseline
From another terminal:
Expect HTTP 200 with data.message equal to "v1". Record data.pid. If the project already has a "dev": "vext dev" script, you can instead run npm run dev -- --port 3000 --verbose-lifecycle.
3. Modify an existing file
Change "v1" above to "v2" and save. Wait for a successful reload result in the terminal, then repeat the request. Expect HTTP 200 and data.message equal to "v2"; under normal soft reload, the PID stays the same.
A detailed log may show the changed file and phase timing, for example:
The timing is illustrative. T1:code describes this batch's modification of an existing backend file. An editor that saves by deleting and recreating the file may instead produce a structural event.
4. Check addition and cold restart
- Copy the example to
src/routes/reload-extra.ts. A request to/reload-extrashould work after the add, normally logged asT2:structural; after deleting the copy, that path should return 404. - Add or change
logger: { level: "debug" }in the existing config object insrc/config/default.ts, retaining other settings. Wait until ready again and request/reload-demo. The response remains v2, but the PID should change. - If the response differs, check for an error or recovery in the terminal. Detection of a file change alone does not prove completion.
Stop the development server with Ctrl+C. Interactive keys such as h and r require a foreground terminal and should not be relied upon in a background pipe.
Logs and frontend updates
By default, the CLI prints the address, startup time, and reload results concisely. --verbose-lifecycle or --startup-profile shows detailed startup output; the former also prints the change list and reload phase timings.
With frontend enabled, pages, components, and static assets take a separate client rebuild path, outputting to .vext/client/ by default. React Fast Refresh, CSS updates, and browser refresh after backend reload are controlled by frontend development settings. See Fast Refresh and Render Refresh. A successful backend request check does not guarantee browser state is preserved.
Three-layer reloading strategy
Classify files by responsibility as cold, soft, client, or ignore. Within soft, use modification type to choose T1 or T2. A Service or Model file may take T1, and a new route may take T2.
Tier 1 — Modify an existing backend file
A modify event for a soft-class file can cover routes, services, middlewares, models, and supported locale sources or resources.
The final replacement is the request entry point, not one route object alone. The existing server socket keeps listening and new requests use the new handler. Requests already inside an old handler continue in its closure, but shared Services and other state it uses may have changed; do not assume in-flight requests are entirely unaffected.
Duration depends on dependencies and reassembly. Compiling a few files can still invalidate a broad dependency graph; reaching the cascade threshold upgrades to a cold restart.
Tier 2 — Add, delete, or reload all sources
An add or delete event for a soft file, or a full source reload requested with h, rescans and compiles entry points before running the same runtime update sequence as T1.
Renaming a file usually appears as deletion plus addition. Editor save behavior and other changes in the same batch can change the final event class.
Tier 3 — Cold restart
Changes to config, plugins, preload, scheduled job directories (default src/jobs, configurable with jobs.dir), root package.json/lockfiles/tsconfig.json, and .env* require Worker reinitialization. Soft reload also upgrades to a cold restart when its loaded dependency graph invalidates a scheduled job. Old tasks stop accepting triggers and drain within the shutdown budget; the new process registers future points. The development parent waits for the old Worker to exit and the new one to become ready; service may be temporarily unavailable and requests can be interrupted.
Changing a middleware definition normally takes soft reload; changing the configured middleware assembly list takes cold restart because config changed. A dependency-file change does not automatically run npm install; install required dependencies yourself first.
Services, Models, and shared state
Affected Services are re-instantiated; unaffected instances normally remain. The framework attempts to call an old instance's optional dispose(), logging a warning if it fails. Application code owns resource cleanup. In-process counters, timers, and long-lived caches should not be assumed to survive reload.
Read the current instance from app.services when needed. Another object's cached reference to an old service is not automatically updated when the property changes. Source import dependencies and dynamically held runtime references are not the same dependency graph.
When database and Model loading are enabled, affected Models attempt to replace registered definitions. This does not run a database migration or rewrite existing records. The following only illustrates a definition file; see Database for complete prerequisites and verification:
Restoring an old Service reference or Model definition cannot reverse external side effects already performed. If runtime updating fails, the overall process cold-restarts to recover a consistent state.
Reload strategy decision table
This table uses the default layout. Resolved directories, event types, and failure recovery can change the actual action:
Explicit external locale and Model read directories are registered separately by the layout, and their changes take the cold path. Watching is not limited to src.
Relationship with vext build
vext dev compiles backend sources into .vext/dev/, and the Worker loads those development outputs. It does not require a prior vext build.
The following uses a TypeScript project with the corresponding scripts. For JavaScript source mode and custom output, see Build:
CLI options
--host :: listens on IPv6 all interfaces; the ready log prints http://[::1]:PORT and bracketed IPv6 Network URLs. Explicit IPv6 hosts are printed as http://[IPv6]:PORT.
By default, vext dev only prints the listening address and total startup time; --startup-profile-json <path> only writes JSON and does not automatically print summary/details. When you need to see the time taken for each stage in the terminal, use --startup-profile explicitly.
File monitoring rules
Monitoring range
By default the watcher covers supported sources and resources under src, public assets, project preload, and specified root configuration/dependency files. With frontend enabled, it also watches resolved role directories such as pages, components, and publicDir, including project-local directories outside src.
Both native fs.watch and polling use snapshots to find additions and deletions. Static assets are not limited to JavaScript or CSS: robots.txt and PDFs can also trigger client rebuild. Backend layout registers custom locale and Model read directories separately.
Ignore rules
Generated and dependency areas (node_modules/, dist/, build/, .vext/, .git/), src/types/generated/, framework temporary modules, and unrecognized file types normally do not enter reload. Root .env* has an explicit cold rule, so not every dot-prefixed file is ignored. Frontend public directories can also include ordinary text assets.
Watch classification and compiler exclusion are separate rules. An ordinary src/**/*.test.ts or src/**/*.d.ts may be classified as soft by the watcher but excluded from compilation. Changing one may produce a skip or compile diagnostic; it does not guarantee a business handler replacement. Put tests under project test/ or tests/ where appropriate, and use TypeScript checking for types.
coldPatterns and ignorePatterns are internal classifier extension points, not currently public config.dev options.
Anti-shake processing
By default, anti-shake is not turned on (debounce: 0), and reloading is triggered immediately after file changes, with the fastest response. If you want to merge multiple changes into one reload when saving in quick succession, you can turn on the debounce window through --debounce <ms>.
For example: if routes/users.ts (Tier 1) and config/default.ts (Tier 3) are modified at the same time, the framework will perform a Tier 3 cold restart (including all changes).
Reload coordination and failure recovery
Frontend watch targets come from the resolved configuration, including custom frontend.root, page/component directories, and publicDir inside the project but outside src/. Native watching and polling cover arbitrary public asset extensions such as robots.txt and PDF, as well as backend .mts/.cts additions and deletions. Restarting after configuration changes updates the watch targets. Mixed frontend/backend batches go to their respective build paths. The development parent receives directory data without executing configuration providers again.
The watcher establishes its baseline before initial checks and startup, so saves received during startup are processed afterward. Saves, manual restarts, and recovery run in sequence; repeated saves are coalesced by path while preserving the final add/delete state. Failed directory reads keep the last complete snapshot and retry with a diagnostic. Falling back to polling preserves pending changes.
Reload completion comes from the Worker's actual result. Compilation failures retain the last valid backend outputs; failures after runtime mutation, or an unconfirmed Worker result, require cold recovery. In an interactive terminal, h reloads all backend sources, r restarts the Worker, and ? shows help. Cold restarts wait for the old Worker to exit and the replacement to finish initialization. Shutdown prevents queued tasks from starting.
Independent projects can run concurrently. Commands that write to the same real project root, including dev, build, and writing typegen, share ownership and report conflicts. typegen --check remains read-only. Each service still needs an available port; do not configure one service's directory as another service's output directory.
vext dev runs dev preflight before startup and reload or restart:
- Basic
typegenrefreshes.vext/types/*.generated.d.ts; TypeScript projects also refreshsrc/types/generated/index.d.ts. - TypeScript semantic diagnostics run asynchronously by default, without delaying ready or reload.
- Blocking basic typegen issues skip the current reload/restart. Use
--strict-preflightif TypeScript semantic diagnostics should also block it.
Route reloads use the compiler's actual project root, source directory, and output directory. A deeper custom output does not change source mapping or the manifest location. .vext/manifest/routes.json is committed only after handler construction and cache clearing succeed; a commit conflict prevents replacement and triggers cold recovery. During initial startup, the manifest is generated first as frontend build input. Use startup completion to establish readiness and the actual reload result to establish that a change took effect.
If soft reload fails before compilation and cache invalidation, the old handler continues serving. A failure after cache invalidation or while reloading locales, middleware, Services, Models, or routes may already have changed shared runtime state, so Vext requests a cold restart. Recovery stops the possibly mixed-state Worker first. If strict preflight blocks its replacement, service remains stopped until the issue is fixed and another save starts a clean process.
A failed frontend build retains the last valid output. A batch with both backend and frontend changes may complete only in part, requiring fuller recovery. Sending a terminal command successfully does not prove the Worker completed it; wait for the result and test an actual request.
TypeScript support
The development backend is transpiled by esbuild. TypeScript semantic diagnostics use the project's local tsc --noEmit. Basic typegen runs first, then type diagnostics run asynchronously by default. The service can be ready before type checking finishes, or while errors are being reported.
- A blocking basic typegen issue stops the current startup or reload.
- Ordinary type errors produce diagnostics but do not block ready or reload by default.
--strict-preflightwaits for semantic diagnostics to pass before startup or reload.- A project without tsconfig skips that TypeScript check; install resolvable local TypeScript for a configured project.
Alternatively set VEXT_DEV_STRICT_PREFLIGHT=1 using the shell syntax in CLI. Type checking does not replace tests, lint, or production build verification. Run npm run typecheck explicitly before a commit or in CI if the script exists.
esbuild uses supported tsconfig compilation options; it does not implement every tsc emit option. Development and production Source Map forms also differ; see Build Source Maps.
FAQ
No reloading is triggered after modification?
- Check the actual watched role and project root — backend source, configured frontend/resource directories, and specified root files have separate rules; arbitrary files outside the roots are not watched.
- Separate watching from compiler exclusion — generated, test, and declaration boundaries are described above; try
--pollon Docker or NFS. - Check the terminal output — whether there is an error message (such as syntax error causing compilation failure)
Behavior not as expected after hot reload?
- Try manual restart — Press
Ctrl+Cto stop and then re-runvext dev - Check whether replacement completed or recovery began — do not delete
.vext/or modify registered artifacts while the service is running. Useror restart the command when a clean process is needed. - Check object references and side effects — affected services update according to module invalidation; cached old references and external side effects are not rolled back automatically. Cold-restart if needed.
Cold restart too slow?
- Measure startup phases first — use
--startup-profile. Moving work toonReady()does not automatically remove the wait before ready; required dependencies must finish before accepting requests. - Reduce optional initialization — disable optional development features; whether a plugin can be disabled depends on its own implementation.
- Use
local.tsto simplify configuration — turn off unnecessary functions (such as current limiting, access logs, etc.) during local development
What to do if the port is occupied?
VextJS no longer promises "internal automatic retries until recovery" if the port is still occupied during a cold restart. The current behavior is to enforce explicit policy by --port-conflict / VEXT_PORT_CONFLICT:
error(default): fail directlyprompt: interactive queryretry/kill/next/abortkill: Try to terminate the occupying processnext: automatically switch to the next available port
If accepting another port is appropriate, select it explicitly:
If you need to manually check the occupied process:
Relationship with Cluster mode
vext dev uses one development parent to manage one application Worker; it does not start multiple production Cluster request Workers. One application Worker does not mean the operating system has only one Node process.
If the production environment requires multiple processes, use vext start with Cluster configuration:
On Windows, vext reload does not support the signal operation. Sending a reload signal successfully also does not prove every replacement completed; see CLI reload.
Best Practices
1. Make full use of Tier 1/2
Keeping most application work in routes and services lets Vext use its targeted reload paths more often. Configuration and plugin changes are less frequent and can use the safer cold-restart path.
2. Cooperate with IDE real-time type checking
Although vext dev will output TypeScript semantic diagnostics, the IDE's live type checking is still the fastest source of feedback. It is recommended to enable IDE prompts and npm run typecheck at the same time; then enable strict mode when blocking preflight is required.
3. Simplified configuration of development environment
Use development.ts to turn off functions that are only needed in the production environment to speed up cold restart:
4. Use _ prefix to share code
Files starting with _ in route and service directories are not automatically loaded as routes or services. When these utility files change:
- Modifying an existing source normally takes T1; adding or deleting one normally takes T2.
- Modules that import it may enter the reverse-dependency invalidation set. Whether a Service rebuilds depends on that set, not merely on the utility file's directory.
- The
_prefix is a loader naming convention; it does not make the compiler or watcher ignore the module.
Next step
- Learn the complete usage of CLI command
- Learn the production environment deployment of Cluster multi-process
- View the environment coverage mechanism of Configuration
- Explore Testing to ensure code correctness after hot reloading