Project structure
VextJS follows the design philosophy of convention over configuration and implements automatic scanning, loading and registration through a fixed directory structure, without the need to manually configure route mapping or service injection.
Standard directory structure
This tree documents the conventions Vext recognizes; it is not a promise that every optional directory is generated. vext create emits only the files with initial runtime content and never uses placeholder README files to keep empty directories in source control.
Detailed explanation of each directory
src/config/ — Configuration directory
When the framework starts, config-loader loads configuration files and merges them deeply in the following order:
Config profiles can be selected with vext start --config <name> or VEXT_CONFIG=<name>. For example, vext start --config sg-sit loads sg-sit.ts.
vext create directly generates local.ts and bootstrap.ts with zero-effect defaults: an empty VextConfigOverride and providers: []. local.ts is excluded by the scaffold .gitignore, so a fresh clone may not contain it and build/start must not depend on it. bootstrap.ts is tracked normally.
Configuration uses deep merge, you only need to declare the fields that need to be covered in the environment file. The middlewares array uses a smart patch strategy (match by name and overwrite) rather than simple array replacement. The provider patch returned by bootstrap.ts will participate in the same merge / validate / freeze process after local.ts and before CLI override.
What does bootstrap.ts do?
When the configuration must be pulled from the remote end before the application is started, src/config/bootstrap.ts can be added:
Common uses:
- Database connection information
- Nacos/Configuration Center startup patch
- Requires infrastructure configuration that is visible before built-in plugins are initialized
src/frontend/ — Frontend directory
The default full-stack scaffold creates src/frontend/ for React page source. URL entry still lives in src/routes/**, and a route handler renders a page with res.render(page, props, options). Vext generates the browser entry and registries under .vext/generated/frontend/.
When config.frontend.enabled is true:
vext devbuilds the client into.vext/client/vext buildwrites production assets todist/client/vext startserves the production client, SSR renderer, and static assets; unmatched HTML fallback depends onfrontend.spaFallback.scopes[]
src/types/ — Application type boundaries
For a new TypeScript full-stack project, vext create emits this explicit
layout:
src/types/shared/is for browser/server-safe contracts that both sides may import.src/types/frontend/is for browser-facing declarations and must not import Node-only or server-only modules.src/types/generated/is framework-owned output. Do not hand-edit it;vext typegenmay update its declarations.
The TypeScript API-only template creates only src/types/generated/, because
it has no frontend consumer. JavaScript templates do not create src/types/.
Vext deliberately does not pre-create src/types/server/: keep a server-only
type next to its route, service, plugin, or Model until a real backend
cross-module contract exists; teams may add a server directory later when that
boundary becomes useful. A type-only contract shared by multiple backend service
consumers belongs in src/types/server/services/<domain>.ts. A DTO shared with
browser code belongs in src/types/shared/<domain>.ts.
Keep TypeScript runtime enums, classes, symbols, initialized constants, and
other values out of src/types/**. Put a runtime value next to its single owner,
or promote a value shared by multiple services to
src/constants/services/<domain>.ts.
This affects only newly scaffolded projects. Existing applications are not
moved, renamed, or deleted, and vext typegen continues to own only
src/types/generated/. Frontend and backend runtime configuration still belongs
under src/config/, not under any type directory.
public/ — Frontend static assets
Files in public/ are copied into the frontend output directory. The default fullstack scaffold emits a transparent vext-mark.svg for AppShell and a contrast-safe favicon.svg; both use the same V geometry. Use public/ for those fixed-URL assets, robots files, and static images that should be served without going through the JavaScript bundle. Images or fonts imported from TSX/CSS and emitted with hashes should usually live under src/frontend/assets/.
src/routes/ — Routing directory
Route files are automatically scanned by router-loader, and file paths are directly mapped to URL prefixes. Each file uses defineRoutes() to export route definitions.
Path mapping rules
Dynamic parameters
Use [paramName] syntax to represent dynamic routing parameters, which are automatically converted to :paramName when loading:
Sub-routes within the file
Multiple sub-routes can be registered inside each file. The path will automatically be spliced with file-level prefixes:
Exclusion rules
The following files will be automatically skipped and will not be loaded as routes:
- Test files:
*.test.ts,*.spec.ts - Files/directories starting with
_or. node_modulesdirectory
src/services/ — Services directory
Service files are automatically scanned by service-loader, each file exports a class, and the constructor receives the app parameter. Automatically mounted to app.services after instantiation.
Name mapping rules
File names are automatically converted from kebab-case to camelCase. Subdirectories are mapped as nested objects.
Service support-code ownership
Do not place support files under src/services/_types or
src/services/_enums. Runtime loading, typegen, Code Docs, and reload tooling
have different consumers; keeping non-service owners outside the scanned
service tree makes the boundary explicit.
Service class writing method
service-loader has built-in circular dependency detection mechanism. If ServiceA directly accesses app.services.b in the constructor, and ServiceB also accesses app.services.a, the framework will detect it at startup and report an error.Recommended practice: Only save the app reference in the constructor, and access other services on demand in the method (delayed access).
src/utils/ — Shared helper functions
Use src/utils/ only for stateless, reusable helpers with a clear boundary,
such as formatting, pure calculations, or stable parsing. Import them normally;
Vext does not auto-scan, instantiate, or inject src/utils/ into app.
Keep a helper next to its domain owner while it has only one consumer. Promote
it to src/utils/ only after real cross-domain reuse appears. Logic that needs
VextApp, a database, request context, or mutable domain state belongs near a
service, plugin, or route instead. Helpers imported by frontend code must remain
browser-safe; do not expose Node-only utilities through a frontend entry.
src/middlewares/ — middleware directory
Middleware files are automatically scanned by middleware-loader. Each file exports a middleware tagged via defineMiddleware or defineMiddlewareFactory.
The file name is the middleware name and is referenced by name in configuration and routing:
When using it, first declare the whitelist in the configuration and then reference it in the routing:
See the Middleware chapter for details.
src/plugins/ — plugin directory
Plug-in files are automatically scanned by plugin-loader, topologically sorted according to dependencies statement, and then setup() is executed in sequence.
See the Plugins chapter for details.
src/locales/ — Internationalization directory
Language pack files are automatically scanned by i18n-loader, and the file name is the language code. After loading, it is registered to the i18n system of schema-dsl and linked with app.throw().
See the Internationalization (i18n) chapter for details.
Automatically scan the loading sequence
When the framework starts (bootstrap), each directory is loaded in the following order:
This order ensures:
- Configuration is ready before all modules
- Plugins can extend the
appobject (e.g. inject database connections) - Middleware is ready before route registration
- Services are injected before routing, and
app.servicescan be safely accessed in the routing handler
package.json requirements
VextJS projects must be declared as ESM modules:
tsconfig.json Recommended configuration
Build product dist/
After executing vext build, the TypeScript files under src/ will be compiled into the dist/ directory, maintaining the same directory structure. In production mode (vext start) load directly from dist/.
:::tip development vs production
vext dev: Load.tsfiles directly fromsrc/(compiled on-the-fly through esbuild), supporting hot reloadingvext start: Load.jsfile fromdist/, you need to executevext buildfirst- When frontend is enabled, production start also requires
dist/client/index.html:::
- When frontend is enabled, production start also requires
Next step
- Learn the three-stage definition and parameter verification of routing
- Configure the Frontend guide
- Understand the design patterns of the Service Layer
- Explore the complete options of Configuration