Configuration items

This page details all configuration fields, types, default values and usage instructions of VextJS.

Configuration loading mechanism

VextJS uses a multi-layer configuration merging strategy, in order of priority from low to high:

DEFAULT_CONFIG (framework built-in default value)
  ↓ Deep merge
src/config/default.ts (project default configuration)
  ↓ Deep merge
src/config/{profile}.ts (config profile, such as production.ts or sg-sit.ts)
  ↓ Deep merge
src/config/local.ts (local override, optional)
  ↓ provider patch
src/config/bootstrap.ts (remote configuration during startup, optional)
  ↓ CLI override
vext start/dev --port --host ...

The merged configuration is deep-frozen through deepFreeze() and cannot be modified at runtime.

Configuration file list

FilePurposeIs it necessary
src/config/default.tsBasic configuration for all profiles
src/config/development.tsDefault development profile overridesOptional
src/config/production.tsDefault production profile overridesOptional
src/config/test.tsDefault test profile overridesOptional
src/config/sg-sit.tsCustom profile overridesOptional
src/config/local.tsLocal override (usually no Git commit)Optional
src/config/bootstrap.tsStartup provider registration entranceOptional

src/config/bootstrap.ts

When the database, key or configuration center patch needs to be injected before the configuration is frozen, you can add:

import { defineBootstrapConfig } from "vextjs";

export default defineBootstrapConfig({
  providers: [
    {
      name: "remote-config",
      timeoutMs: 10_000,
      async load({ configProfile, signal, baseConfig }) {
        const response = await fetch(
          `https://config.example.com/${configProfile}.json`,
          { signal },
        );
        const remote = await response.json();
        return {
          database: remote.database,
          logger: {
            lifecycleLevel: baseConfig.logger?.lifecycleLevel ?? "concise",
          },
        };
      },
    },
  ],
});

Constraints:

  • provider must return plain object patch or null
  • patch only supports JSON-like structure
  • When required is not declared: production defaults to fail-fast, development/test defaults to continue after warning
  • In Cluster mode, the same provider patch will be reused in the same startup cycle to prevent Master / Worker from seeing different results.

Configuration file example

// src/config/default.ts
export default {
  port: 3000,
  adapter: "native",
  cors: {
    enabled: true,
    origins: ["http://localhost:3000"],
  },
  logger: {
    level: "debug",
  },
};
// src/config/production.ts
export default {
  port: 8080,
  cors: {
    origins: ["https://api.example.com"],
  },
  logger: {
    level: "warn",
  },
  response: {
    hideInternalErrors: true,
  },
};

Complete configuration reference

VextConfig

FieldTypeDefault ValueDescription
portnumber3000HTTP listening port
hoststring'0.0.0.0'HTTP listening address
adapterstring | Function | VextAdapter'native'Low-level adapter
trustProxybooleanfalseWhether to trust the proxy
middlewaresVextMiddlewareConfig[][]Route-level middleware whitelist
corsVextCorsConfigSee belowCORS configuration
rateLimitVextRateLimitConfigSee belowRate limit configuration
requestIdVextRequestIdConfigSee belowRequest ID configuration
loggerVextLoggerConfigSee belowLog configuration
shutdownVextShutdownConfigSee belowGraceful shutdown configuration
serverVextServerConfig{}Node.js HTTP server configuration
responseVextResponseConfigSee belowResponse configuration
bodyParserVextBodyParserConfigSee belowBody parsing configuration
multipartVextMultipartConfigundefinedFile upload configuration
accessLogVextAccessLogConfigSee belowAccess log configuration
openapiVextOpenAPIConfigSee belowOpenAPI documentation configuration
requestContextVextRequestContextConfigSee belowRequest context configuration
fetchVextFetchConfigSee belowBuilt-in HTTP client and proxy configuration
frontendboolean | VextFrontendConfig{ enabled: false }Built-in frontend build and static serving configuration
clusterPartial<VextClusterConfig>undefinedCluster multi-process configuration

adapter

The underlying HTTP adapter supports three parameter passing methods:

// Method 1: String identification (built-in adapter)
export default {
  adapter: "native", // 'native' | 'hono' | 'fastify' | 'express' | 'koa'
};

// Method 2: Factory function (pass in custom options)
import { fastifyAdapter } from "vextjs/adapters/fastify";

export default {
  adapter: fastifyAdapter({ bodyLimit: 5 * 1024 * 1024 }),
};

//Method 3: Custom adapter instance (implementing VextAdapter interface)
export default {
  adapter: myCustomAdapter,
};

trustProxy

When set to true:

  • req.ip reads the first IP from the X-Forwarded-For request header
  • req.protocol is read from the X-Forwarded-Proto request header

This option needs to be enabled when deployed behind Nginx/cloud load balancer.

middlewares

Route-level middleware whitelist declaration. Only middleware declared here can be referenced in routes options.middlewares.

export default {
  middlewares: [
    { name: "auth" },
    { name: "admin", options: { role: "admin" } },
    { name: "client-cache", options: { maxAge: 60 } },
  ],
};
Tip

Global middleware (such as CORS, body-parser) is automatically registered by the framework and does not need to be declared here. Only routing-level optional middleware is declared here.


VextCorsConfig

Cross-domain resource sharing configuration.FieldTypeDefault ValueDescription
enabledbooleantrueWhether to enable CORS
originsstring[]['*']Allowed origin domain names
methodsstring[]['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']Allowed HTTP methods
headersstring[]['Content-Type', 'Authorization', 'X-Request-Id']Allowed request headers
credentialsbooleanfalseWhether to allow carrying credentials
maxAgenumberundefinedCORS preflight result cache time (seconds)
export default {
  cors: {
    enabled: true,
    origins: ["https://app.example.com", "https://admin.example.com"],
    credentials: true,
    maxAge: 86400,
  },
};
Warning

origins: ['*'] and credentials: true cannot be used at the same time. When you need to carry credentials, you must specify a specific domain name.


VextRateLimitConfig

Global rate limit configuration, implemented based on flex-rate-limit.

FieldTypeDefault ValueDescription
enabledbooleantrueWhether to enable rate limiting
maxnumber100Maximum number of requests within the time window
windownumber60Time window (seconds)
messagestring'Too Many Requests'Overrun error message
keyBystring | Function'ip'Request source identifier
export default {
  rateLimit: {
    max: 200,
    window: 120,
    //Limit flow by user ID (requires auth middleware to parse the user first)
    keyBy: (req) => req.user?.id ?? req.ip,
  },
};

keyBy option

valuedescription
'ip'Limit flow by client IP (default)
'user'Press req.user?.id to limit current
(req) => stringCustom function, returns unique identifier
Tip

The routing level can override the global configuration via options.override.rateLimit, or set it to false to disable rate limiting.


VextRequestIdConfig

Request ID tracing configuration for log correlation and distributed link tracing.

FieldTypeDefault ValueDescription
enabledbooleantrueWhether to enable request ID
headerstring'x-request-id'From which request header to read (gateway transparent transmission)
responseHeaderstring'x-request-id'The name to write the response header
generate() => stringcrypto.randomUUID()Custom ID generation function

requestId vs traceId

requestId is the unique identifier of the request built into vext, and traceId usually refers to the tracing ID generated by the APM link tracking system (such as OpenTelemetry / Jaeger). Both have different usage scenarios:

Mode 1: requestId acts as traceId (simple scenario)

Change the request header name of requestId to x-trace-id to unify it with the link tracking header, which is suitable for systems that do not rely on external APM:

import { nanoid } from "nanoid";

export default {
  requestId: {
    header: "x-trace-id", // Read from x-trace-id (gateway injection)
    responseHeader: "x-trace-id", // Write back the response header
    generate: () => nanoid(), // Can be replaced by a shorter ID generator
  },
};

Mode 2: requestId + APM traceId coexist (enterprise-level scenario)

Keep requestId (log association), and transparently transmit APM's traceparent header through config.fetch.propagateHeaders, suitable for connecting to OpenTelemetry / Jaeger and other systems:

export default {
  // requestId retains the default configuration (for log correlation)
  requestId: {
    header: "x-request-id",
    responseHeader: "x-request-id",
  },
  // APM tracking headers are automatically transparently transmitted to downstream services through propagateHeaders
  fetch: {
    propagateHeaders: ["traceparent", "tracestate"],
  },
};
Select suggestions
  • Internal system, simple tracing → Mode 1 (rename header to x-trace-id)
  • Access OpenTelemetry / Jaeger / Datadog → Mode 2 (retain requestId, configure propagateHeaders)
  • For details, see [Request context → Relationship with distributed tracing](/guide/request-context#Relationship with distributed tracing traceId) :::

Generators can also be replaced dynamically via plugins:

app.setRequestIdGenerator(() => myCustomId());

VextFetchConfig

Built-in HTTP client and request proxy configuration.

FieldTypeDefault ValueDescription
timeoutnumber10000app.fetch and app.fetch.proxy default timeouts
retrynumber0The default number of retries, indicating the number of additional attempts
retryDelaynumber | (attempt: number) => number1000Default retry interval, supports function form
propagateHeadersstring[][]Common app.fetch request header whitelist for automatic transparent transmission
proxyVextFetchProxyTargetConfig[][]List of upstream targets for app.fetch.proxy.<name>()
export default {
  fetch: {
    timeout: 10_000,
    retry: 1,
    retryDelay: 500,
    propagateHeaders: ["traceparent", "x-tenant-id"],
    proxy: [
      {
        name: "userService",
        baseURL: "http://user-service:3001/api",
        forwardHeaders: ["x-tenant-id"],
        headers: { "x-source": "gateway" },
        timeout: 5000,
        retry: 1,
      },
    ],
  },
};

VextFetchProxyTargetConfig

FieldTypeRequiredDescription
namestringTarget name, corresponding to app.fetch.proxy.<name>(); reserved name then cannot be used
baseURLstringUpstream base URL
headersRecord<string, string>Target-level fixed request headers
forwardHeadersstring[]Whitelist of request headers transparently transmitted from the current req.headers
defaultInjectHeadersRecord<string, string> | FunctionTarget-level dynamic injection headers
allowAuthorizationForwardbooleanWhether to allow transparent transmission of the original Authorization
timeoutnumberTarget-level timeout
retrynumberNumber of target-level retries
retryDelaynumber | (attempt: number) => numberTarget-level retry interval

Proxy request header priority: target.headers < forwardHeaders < target.defaultInjectHeaders < options.headers < options.injectHeaders. Authorization does not transmit transparently by default, and both whitelist and allowAuthorizationForward: true must be configured.Agent retry priority: options.retry > target.retry > config.fetch.retry > 0. Only GET / HEAD / OPTIONS / PUT / DELETE will automatically retry when upstream 5xx or network error occurs; POST / PATCH does not retry by default, does not retry when timeout and returns local 504.


VextLoggerConfig

| Structured log configuration, implemented based on Vext’s built-in logger kernel. | Field | Type | Default Value | Description | | --------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------------- | -------- | -------------- | ------------------------------ | | level | 'fatal' \| 'error' \| 'warn' \| 'info' \| 'debug' \| 'trace' \| 'silent' | 'info' | log level | | lifecycleLevel | 'concise' \| 'verbose' | 'concise' | Framework life cycle log detail level, control system log output such as startup, loader, hot reload, cluster, etc. | | pretty | boolean | Development environment true | Whether to use the built-in pretty formatter to output readable format | | prettyIgnore | string | 'pid,hostname,requestId' | Fields to ignore in pretty mode (comma separated). Hiding requestId by default prevents mixin-injected fields from being expanded into multi-line noise, and the production environment JSON output is not affected | | prettySingleLine | boolean | true | Whether to compress extra fields in the same line of the message as JSON inline in pretty mode. Set to false to use multi-line expansion format. Only affects pretty mode, production environment JSON output is not affected | | redactKeys | string[] | [] | Desensitize structured log fields by exact key at any level. The top level level is the log protocol field and will not be overwritten | | redactPaths | string[] | [] | Desensitize structured log fields by dot notation exact path, support array numeric subscript; do not support wildcard, bracket notation, remove or function censor | | redactValue | string | '[Redacted]'' | Desensitized replacement value | | mixin |() => Record<string, unknown> |undefined | Customized log mixin function, the return value will be merged with the framework's built-in fields and injected into each log.requestIdis a framework protected field and cannot be overridden by user mixin; other fields such astrace_id/span_idare given priority by user mixin. Typical use: Inject OpenTelemetrytrace_id/span_id` to associate logs with link tracking. User mixin calls will not be executed when not configured. |

export default {
  logger: {
    level: "debug",
    pretty: true, // Development environment beautification output
    // prettySingleLine: true, // Default value, extra fields are compressed to the same line of the message
    // prettySingleLine: false, // Restore multi-line expansion format
    // prettyIgnore: 'pid,hostname,requestId', //Default value, hide requestId
    // prettyIgnore: 'pid,hostname', // To display requestId in pretty mode
    // redactKeys: ['password', 'token'],
    // redactPaths: ['user.email', 'headers.authorization'],
    // redactValue: '[Redacted]',
  },
};

Log level priority (from high to low):

fatal > error > warn > info > debug > trace

After setting a certain level, only logs of this level and higher will be output. Set to 'silent' to be completely silent.

The default logger also supports runtime app.logger.getLevel() / app.logger.setLevel(level) to adjust subsequent log thresholds; the configuration object itself will still be frozen after startup and should not be dynamically changed by modifying app.config.logger.level.


VextShutdownConfig

Graceful shutdown of configuration.

FieldTypeDefault ValueDescription
timeoutnumber10Shutdown timeout (seconds)

After receiving the SIGTERM / SIGINT signal, the framework will:

  1. Stop accepting new requests
  2. Wait for the in-flight request to complete (no more than timeout seconds)
  3. Execute all onClose hooks in LIFO order
  4. Exit the process
export default {
  shutdown: {
    timeout: 30, // Container environment recommends 30 seconds
  },
};

VextServerConfig

Inbound Node.js HTTP server layer configuration. Applicable to built-in Native / Hono / Fastify / Express / Koa adapter, also applicable to development server created by vext dev. Unset fields retain the current Node.js default value.

FieldTypeDefault ValueDescription
requestTimeoutnumberNode.js default valueMaximum time in milliseconds to receive a complete request, 0 means disabled
headersTimeoutnumberNode.js default valueMaximum time to receive complete HTTP headers (milliseconds)
keepAliveTimeoutnumberNode.js default valueKeep-alive idle wait time after response completes (milliseconds)
socketTimeoutnumberNode.js default valuesocket inactivity timeout (milliseconds), 0 means disabled
maxHeaderSizenumberNode.js default valueMaximum request header size (bytes)
maxRequestsPerSocketnumberNode.js default valueThe maximum number of requests for a single socket, 0 means unlimited
connectionsCheckingIntervalnumberNode.js default valueOutstanding request timeout check interval (milliseconds)
export default {
  server: {
    requestTimeout: 120_000,
    headersTimeout: 60_000,
    keepAliveTimeout: 5_000,
    socketTimeout: 0,
    maxHeaderSize: 16 * 1024,
    maxRequestsPerSocket: 0,
    connectionsCheckingInterval: 30_000,
  },
};

config.server only controls inbound service requests. The timeout for outbound app.fetch / app.fetch.proxy is controlled by config.fetch.timeout, the proxy target timeout, or options when calling.


VextResponseConfig

Response format configuration.FieldTypeDefault ValueDescription
hideInternalErrorsbooleantrueWhether to hide 500 error details
wrapbooleantrueWhether to enable export packaging

Export packaging

When wrap: true is enabled, res.json(data) is automatically wrapped:

{
  "code": 0,
  "data": { "id": 1, "name": "Alice" },
  "requestId": "550e8400-e29b-41d4-a716-446655440000"
}

Error response format:

{
  "code": 10001,
  "message": "User does not exist",
  "requestId": "550e8400-e29b-41d4-a716-446655440000"
}

When wrap: false is disabled, res.json(data) sends raw data directly.

Hide internal errors

hideInternalErrors only affects the "unknown exception" 500 error path, such as the scenario where throw new Error("...") is directly used in routing, service, and middleware. It does not change the status code and response format of structured errors such as app.throw(...) or VextValidationError.

When hideInternalErrors: true is used, 500 errors are not exposed stack trace:

//hideInternalErrors: true
{ "code": 500, "message": "Internal Server Error" }

// hideInternalErrors: false (for development environment only)
{ "code": 500, "message": "Internal Server Error", "stack": "..." }

VextBodyParserConfig

Request body parsing configuration.

FieldTypeDefault ValueDescription
enabledbooleantrueWhether to enable body parsing
maxBodySizestring | number'1mb'Maximum request body size
export default {
  bodyParser: {
    maxBodySize: "5mb", // Supports 'kb', 'mb', 'gb' units
  },
};

After disabled, req.body is always undefined, which is suitable for pure GET service or custom body parsing scenarios.

maxBodySize supported formats:

FormatExampleDescription
String'1mb', '512kb', `'10mb''Support kb/mb/gb unit
Number1048576Directly specify the number of bytes

VextMultipartConfig

Multipart/File upload global configuration.

FieldTypeDefault ValueDescription
enabledbooleanfalseWhether to enable built-in multipart parsing. After setting to true, body-parser will automatically fill in req.files without plug-in
maxFileSizenumber10485760Maximum size of a single file (bytes, default 10MB)
maxFilesnumber10Maximum number of files in a single request
allowedMimeTypesstring[]undefinedWhitelist of allowed MIME types (if not set, there will be no restriction)
export default {
  multipart: {
    enabled: true, // Enable built-in parsing
    maxFileSize: 10 * 1024 * 1024, // 10MB
    maxFiles: 5,
    allowedMimeTypes: [
      "image/jpeg",
      "image/png",
      "image/gif",
      "application/pdf",
    ],
  },
};

:::tip Fastify linkage multipart.maxFileSize only limits the size of a single file; the total request body read limit is controlled by bodyParser.maxBodySize. When using Fastify, if fastifyAdapter({ bodyLimit }) is additionally passed in, the actual read boundary will be the smaller value of the adapter bodyLimit and the overall upper limit of body-parser.


VextAccessLogConfig

Access log configuration, implemented based on onion model after-middleware.FieldTypeDefault ValueDescription
enabledbooleantrueWhether to enable access logs
levelstring'info'Basic log level, only supports 'info' or 'debug'
skipPathsstring[][]Exact match skipped path list
skipPathPrefixesstring[][]List of paths skipped by prefix matching
slowThresholdnumber0Slow request threshold, 0 means not enabled
warnOn4xxbooleanfalseWhether to promote 4xx responses to warn
logResponseSizebooleanfalseWhether to append the response body size at the end of the message
export default {
  accessLog: {
    enabled: true,
    level: "info",
    skipPaths: ["/health", "/readiness", "/metrics"],
    skipPathPrefixes: ["/internal"],
    slowThreshold: 1000,
    warnOn4xx: false,
    logResponseSize: false,
  },
};

Access log output example:

POST /api/users 201 12ms | 192.168.1.1

Message fields include HTTP method, path, status code, response time (ms) and client IP; requestId is automatically injected into the JSON record field by logger's AsyncLocalStorage mixin.


VextOpenAPIConfig

| OpenAPI documentation automatically generates configuration. | Field | Type | Default Value | Description | | ------------------------------------------------------------ | ---------------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -------- | -------- | ---- | ------------------------------------------------------------------------------- | | enabled | boolean | dev is enabled, prod is closed | Whether to enable | | title | string | undefined | Document title | | version | string | undefined | Document version number | | description | string | undefined | Document description | | docsPath | string | '/docs' | Scalar document path | | jsonPath | string | '/openapi.json'' | OpenAPI JSON path | | jsonPublicPath |string | Same asjsonPath | Public access path of OpenAPI spec (only affects the URL referencing the spec in Scalar HTML, not routing registration). Used for reverse proxy stripping prefix scenarios, [see the guide for details](/guide/openapi#reverse proxy path prefix scenario) | |contact |object |undefined | Contact information | |license |object |undefined | License information | |servers |array |undefined | Server address list | |tags |array |undefined | Global tag definition | |guardSecurityMap |Record<string, string> |undefined | Guard → Security Scheme mapping | |securitySchemes |object |undefined | Security scheme definition | |scalar|object|{}| Scalar API Reference UI configuration (theme, dark mode, layout, favicon, etc.) | |scalar.theme |string |'default' | Theme:'default'\|'moon'\|'purple'\|'solarized'\|'bluePlanet'\|'saturn'\|'kepler'\|'mars'\|'deepSpace'\|'none' | |scalar.darkMode |boolean |false | Whether to enable dark mode | |scalar.layout |string |'modern' | Layout mode:'modern'(three columns) \|'classic'(two columns) | |scalar.favicon |string |undefined | Document page favicon URL (such as'/favicon.svg') | | scalar.sources |array |undefined | Multiple OpenAPI documentation sources ([see guide for details](/guide/openapi#import external-openapi)). Each item containstitle, urlorcontent, slug | |scalar.cdnUrl |string | jsDelivr CDN | Customize Scalar JS loading address ([see guide for details](/guide/openapi#Use custom address to override local service)). Applicable to intranet/offline/version locked | |scalar.showSidebar |boolean |true | Whether to display the sidebar | |scalar.hideModels |boolean |false | Whether to hide the Models/Schemas panel | |scalar.hiddenClients |string[] |undefined | List of hidden client languages (e.g.['php', 'ruby']) | | scalar.searchHotKey |string |'k' | Search hotkey (Ctrl+K / Cmd+K) | |scalar.proxyUrl |string |undefined | Proxy URL (Try it out request to avoid CORS) | |scalar.customCss |string |undefined | Custom CSS | | ~~tryItOutEnabled~~ | boolean |true | ~~Deprecated~~ Scalar has built-in Try it out, no separate configuration is required | | ~~docExpansion~~ | 'none' | 'list' | 'full'|'list' | ~~Deprecated~~ Please usescalar.layout` instead |

export default {
  openapi: {
    enabled: true,
    title: "My API",
    version: "1.0.0",
    description: "My API Documentation",
    scalar: {
      theme: "default",
      darkMode: false,
      layout: "modern",
      favicon: "/favicon.svg",
    },
    servers: [
      { url: "http://localhost:3000", description: "Development environment" },
      { url: "https://api.example.com", description: "Production environment" },
    ],
    tags: [
      { name: "User", description: "User management interface" },
      { name: "Order", description: "Order Management Interface" },
    ],
    securitySchemes: {
      bearerAuth: {
        type: "http",
        scheme: "bearer",
        bearerFormat: "JWT",
      },
    },
    guardSecurityMap: {
      auth: "bearerAuth",
    },
  },
};

guardSecurityMap

Automatically map routing middleware names to OpenAPI Security Scheme:

//Use auth middleware in route declaration
app.get("/profile", { middlewares: ["auth"] }, handler);
// ↑ OpenAPI automatically infers that this route requires bearerAuth authentication

securitySchemes

Supported security scheme types:

typeDescriptionRequired fields
httpHTTP authenticationscheme (bearer / basic)
apiKeyAPI Keyname, in (header / query / cookie)
oauth2OAuth 2.0
openIdConnectOpenID Connect

VextRequestContextConfig

AsyncLocalStorage request context configuration.

FieldTypeDefault ValueDescription
enabledbooleantrueWhether to enable the request context
export default {
  requestContext: {
    enabled: false, // Disabled to increase RPS by 3-8%
  },
};

:::warning After disabling, the following functions will be disabled:

  • Logger automatically injects requestId
  • app.throw() automatically parses request-level locale
  • app.fetch() automatically propagates requestId :::

VextFrontendConfig

Built-in frontend build and static serving configuration.

FieldTypeDefault ValueDescription
enabledbooleanfalseWhether to enable frontend integration
frameworkstring'react'Frontend framework label
rootstring'src/frontend'Frontend source directory
pages.dirstring'pages'Page directory resolved from root
pages.extensionsstring[]['.tsx', '.jsx', '.ts', '.js']Extensions scanned for pages, layouts, error pages, and locale modules
pages.documentstring'pages/_document.html'Document template path resolved from root
pages.errorDirstring'pages/error'Error page directory resolved from root
componentsDirstring'components'Shared component directory resolved from root
styles.entrystring'styles/index.css'Global CSS entry resolved from root
styles.jscssboolean | object{ enabled: true }Vext JSCSS build-time CSS extraction and dynamic CSS variables
styles.jscss.filesstring[]['**/*.style.ts', '**/*.style.js', '**/*.css.ts']JSCSS source file glob patterns
styles.jscss.runtimeAdapter'css-variables' | 'none' | false'css-variables'Runtime output mode for dynamic style variables
styles.jscss.dynamicVarsbooleantrueWhether to emit dynamic CSS variables
styles.jscss.recipesbooleantrueWhether to emit recipe style combinations
assetsDirstring'assets'Bundled frontend asset directory resolved from root
entrystring'.vext/generated/frontend/browser-entry.tsx'Generated browser entry; usually not written by hand
indexHtmlstring'src/frontend/pages/_document.html'HTML document template
outDirstring.vext/client in dev, dist/client in productionFrontend output directory
publicDirstring'public'Static assets copied into the frontend output
publicPathstring'/'Public asset path prefix
aliasobjectBuilt-in @frontend/@pages/@components/@styles/@assetsFrontend-safe aliases; no default alias points at all of src
spaFallbackboolean | object{ scopes: [] }Serve fallback only for explicitly declared client-router sub-app scopes
spaFallback.enabledbooleantrueEnables scoped fallback arbitration; with no scopes, no path is captured
spaFallback.excludestring[]['/api/**', '/openapi.json', '/docs/**']Global fallback exclusion paths
spaFallback.scopes[]object[][]Explicit client-router sub-app scopes
apiClientboolean | objecttrueGenerate client contract artifacts
apiClient.enabledbooleantrueWhether to emit client-contract.json and api.generated.ts
render.ssrbooleantrueEnable SSR rendering
render.fallback'client' | 'error''client'Whether SSR failures fall back to a client shell or an error response
render.timeoutMsnumber3000Timeout for one SSR render
render.layoutbooleantrueWhether to enable the nested layout chain
errorPages.defaultstring'error/default'Default error page id
errorPages.statusobject{ 404: 'error/404', 500: 'error/500' }Status code to error page id mapping
i18nobject{ enabled: false }Frontend page message layer, SSR messages, and {vext.lang}
i18n.sourcestring'locales'Frontend message directory resolved from root
i18n.defaultLocale'inherit' | string'inherit'Default locale; inherit follows the request-level locale
i18n.detectstring[]['accept-language']SSR locale detection sources
i18n.inject'used' | 'all''used'Whether to inject used messages or all messages
i18n.clientSwitch'reload''reload'Initial client locale switch strategy
i18n.clientLoad'current' | 'all''current'Whether the browser loads only the current SSR locale or all locales
i18n.htmlLangbooleantrueWhether to write {vext.lang} / <html lang>
i18n.varybooleantrueWhether locale affects response vary/cache behavior
dev.hotbooleantrueDevelopment frontend hot update channel
dev.fastRefreshbooleantrueReact Fast Refresh
dev.transport'sse''sse'Transport for the Vext dev event bus
dev.overlaybooleantrueWhether to enable the development error overlay
dev.debounceMsnumber50File change event debounce interval
dev.renderRefresh'prompt' | 'auto' | 'off''prompt'Browser behavior after render-related route/service backend changes
build.targetstring | string[]'es2022'Browser build target
build.minifybooleanProduction trueMinify frontend output
build.sourcemapbooleanDevelopment trueGenerate frontend source maps
build.clientobjectInherits shared build defaultsBrowser bundle output, hash names, splitting, and external entries
build.client.externalstring[][]Modules externalized from the browser bundle
build.client.externalRuntimeobject{}Import map URL mapping for externalized browser modules; React externals fail without mappings
build.serverobjectserver/renderer.cjsSSR renderer bundle output
build.vendorChunksboolean | object{ enabled: true }Vext-managed vendor entry and shared chunk handling
build.budgetsobjectAll 0Frontend asset budgets; 0 disables a budget
build.budgets.maxInitialJsGzipBytesnumber0Initial JS gzip budget
build.budgets.maxInitialJsBrotliBytesnumber0Initial JS brotli budget
build.budgets.maxRouteInitialJsBrotliBytesnumber0Per-route initial JS brotli budget
build.budgets.maxAppOwnedInitialJsBrotliBytesnumber0App-owned initial JS brotli budget excluding external runtime assets
build.assets.inlineLimitnumber0Imported asset inline limit; default emits hashed files
build.css.modulesbooleantrueWhether to support the CSS Modules convention
build.diagnostics.metafilebooleantrueWhether to keep internal esbuild metafile diagnostics for size report / leak scan
build.diagnostics.sizeReportbooleantrueWhether to emit a size report
build.diagnostics.performanceReportbooleantrueWhether to keep route initial assets, compressed budget, and browser probe report data
build.diagnostics.leakScanbooleantrueBlocks browser bundles from importing server-only modules
deploy.assetBaseUrlstringNoneAbsolute CDN prefix for static frontend assets
deploy.crossOrigin'anonymous' | 'use-credentials'Nonecrossorigin value injected into script/link tags
deploy.integritybooleanfalseInject build-time SRI into generated JS/CSS tags
deploy.uploadboolean | object{ enabled: false, exclude: ["**/*.map"] }Static asset upload config; vext deploy assets uploads incrementally by sha256
export default {
  frontend: {
    enabled: true,
    framework: "react",
    publicDir: "public",
    publicPath: "/",
    spaFallback: {
      scopes: [
        {
          basePath: "/admin/app",
          page: "admin/app/shell",
          exclude: ["/admin/api/**"],
        },
      ],
    },
  },
};

By default spaFallback.scopes is empty, so unknown HTML paths are not swallowed into the SPA. For mixed SSR + client-router sub-apps, declare each basePath in scopes[]. spaFallback: true is kept only as a compatibility shorthand and is not recommended for enterprise mixed projects.


VextClusterConfig

Cluster multi-process configuration. For the complete interface definition, see src/types/app.ts VextClusterConfig.

Basic fields

FieldTypeDefault ValueDescription
enabledbooleanfalseWhether to enable Cluster mode (can also be enabled by VEXT_CLUSTER=1)
workers'auto' | 'auto-1' | number'auto'Number of Workers ('auto' = number of CPU cores; 'auto-1' = number of CPU cores - 1; number = fixed number, clamped to [1, 64])
autoRestartbooleantrueWorker automatically restarts after crash
maxRestartsnumber5The maximum number of restarts allowed within the fast restart detection window
restartWindownumber60000Fast restart detection window (milliseconds)
restartBaseDelaynumber1000Restart interval backoff base (milliseconds)
restartMaxDelaynumber30000Upper limit of restart interval (milliseconds)
pidFilestring'.vext.pid'PID file path (for vext stop / vext reload to locate the process)
titlePrefixstring'vext'Worker process title prefix
sticky'none' | 'ip''none'Sticky session mode ('ip' allocates fixed Worker based on client IP, suitable for WebSocket/SSE)

healthCheck — heartbeat detection

FieldTypeDefault ValueDescription
healthCheck.enabledbooleantrueWhether to enable Worker heartbeat detection
healthCheck.intervalnumber15000The interval at which the Master sends heartbeat detections (milliseconds)
healthCheck.timeoutnumber30000Worker heartbeat timeout threshold (milliseconds), forced restart after timeout

reload — Zero-downtime rolling restart

FieldTypeDefault ValueDescription
reload.workerDelaynumber2000Time to wait before replacing the next Worker (milliseconds)
reload.readyTimeoutnumber30000New Worker readiness timeout (milliseconds)
reload.shutdownTimeoutnumber10000Old Worker shutdown timeout (milliseconds)
export default {
  cluster: {
    enabled: true,
    workers: "auto", // Take full advantage of all CPU cores
    autoRestart: true,
    maxRestarts: 5,
    healthCheck: {
      enabled: true,
      interval: 15000,
      timeout: 30000,
    },
    reload: {
      workerDelay: 2000,
      readyTimeout: 30000,
      shutdownTimeout: 10000,
    },
  },
};

It can also be enabled through environment variables (no need to modify the configuration file):

VEXT_CLUSTER=1 vext start

VextCacheConfig

Route-level response cache global configuration.FieldTypeDefault ValueDescription
enabledbooleantrueWhether to enable route-level response caching. When set to false, the cache middleware will not be installed and the Redis/MultiLevel connection will not be opened
defaultTtlnumber60000The default value when the route does not specify a TTL, in milliseconds
maxEntriesnumber1000Memory mode quick configuration: maximum number of cache entries
maxMemorynumberMemory mode quick configuration: maximum memory usage bytes
cleanupIntervalnumber0Memory mode quick configuration: periodic cleaning interval, 0 means only lazy cleaning
cacheHubobjectMemoryUnderlying response cache runtime configuration
export default {
  cache: {
    enabled: true,
    defaultTtl: 120_000,
    maxEntries: 2000,
  },
};

Memory complete configuration:

export default {
  cache: {
    defaultTtl: 60_000,
    cacheHub: {
      mode: "memory",
      maxEntries: 1000,
      maxMemory: 50 * 1024 * 1024,
      cleanupInterval: 30_000,
      enableStats: true,
    },
  },
};

Redis configuration:

export default {
  cache: {
    defaultTtl: 2_000,
    cacheHub: {
      mode: "redis",
      url: "redis://localhost:6379",
      deleteCommand: "unlink",
      lease: {
        waitForOwner: 1_000,
        onTimeout: "fetch",
      },
      distributed: {
        channel: "vext:response-cache",
      },
    },
  },
};

MultiLevel configuration:

export default {
  cache: {
    defaultTtl: 60_000,
    cacheHub: {
      mode: "multi-level",
      memory: { maxEntries: 1000 },
      redis: { url: "redis://localhost:6379" },
      writePolicy: "both",
      backfillOnRemoteHit: true,
      remoteTimeout: 50,
      lease: true,
    },
  },
};

cacheHub only accepts response-cache-kit/cache-hub configuration and does not accept custom Store. Route-level response caching is configured via RouteOptions.cache. The public configuration unit is in milliseconds; the Cache-Control: max-age in the response header will output seconds according to the HTTP standard. See the Response Caching Guide for details.


DEFAULT_CONFIG

The full value of the framework’s built-in default configuration:

import { DEFAULT_CONFIG } from 'vextjs';

// Complete content of DEFAULT_CONFIG:
{
  port: 3000,
  host: '0.0.0.0',
  adapter: 'native',
  trustProxy: false,
  middlewares: [],
  cors: {
    enabled: true,
    origins: ['*'],
    methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
    headers: ['Content-Type', 'Authorization', 'X-Request-Id'],
    credentials: false,
  },
  rateLimit: {
    enabled: true,
    max: 100,
    window: 60,
    message: 'Too Many Requests',
    keyBy: 'ip',
  },
  requestId: {
    enabled: true,
    header: 'x-request-id',
    responseHeader: 'x-request-id',
  },
  logger: {
    level: 'info',
  },
  shutdown: {
    timeout: 10,
  },
  response: {
    hideInternalErrors: true,
    wrap: true,
  },
  bodyParser: {
    enabled: true,
    maxBodySize: '1mb',
  },
  accessLog: {
    enabled: true,
    level: 'info',
    skipPaths: [],
  },
  openapi: {
    enabled: false,
  },
  requestContext: {
    enabled: true,
  },
  frontend: {
    enabled: false,
  },
}

VextUserConfig

User-configured input type, all fields are optional. The complete VextConfig is generated by loadConfig() merging the default values.

import type { VextUserConfig } from "vextjs";

const config: VextUserConfig = {
  port: 8080,
  logger: { level: "debug" },
};

export default config;

loadConfig

Configuration loading function, receives the configuration directory path and performs the complete configuration chain merge.

import { loadConfig } from "vextjs";
import { join } from "node:path";

const config = await loadConfig(join(process.cwd(), "src/config"), {
  rootDir: process.cwd(),
  command: "start",
  isBuilt: false,
});
// config: VextConfig (merged, frozen)

Usually there is no need to call it manually, bootstrap() will automatically call loadConfig() internally. The merge order is: DEFAULT_CONFIG < default < config profile < local < bootstrap provider patch < CLI override.


Environment variable override

Some configurations support overriding through environment variables:

Environment variablesCorresponding configurationDescription
PORTportHTTP listening port
HOSThostHTTP listening address
VEXT_CONFIGSelect the config profile to load
NODE_ENVRuntime mode; vext start runs as production
VEXT_CLUSTERcluster.enabledSet to 1 to enable clustering
PORT=8080 VEXT_CONFIG=sg-sit vext start

Type declaration extension

Plug-ins can add custom fields to VextConfig through declare module:

// types/vext.d.ts
declare module "vextjs" {
  interface VextConfig {
    redis?: {
      host: string;
      port: number;
      password?: string;
    };
  }
}

Later use in the configuration file will get full type hints:

// src/config/default.ts
export default {
  redis: {
    host: "localhost",
    port: 6379,
  },
};