docs / Language

Configuration

vesk.config.js / vesk.config.ts, defineConfig and how it normalizes security, environment loading, platforms, and the app-level events + context system.

Vesk projects are configured with `vesk.config.js` or `vesk.config.ts` in the project root. TypeScript configs are transpiled inline at startup — no separate build step needed.

Basic config

vesk.config.ts
									
										import { defineConfig } from '@vesk/compiler'

export default defineConfig({
  appDir: './app',
  outDir: '.vesk',
  publicDir: './public',
  security: 'strict',
  md: {
    html: 'escape',
  },
})
									
								

Loading

  • The CLI looks for `vesk.config.js` first, then `vesk.config.ts`; if neither exists, the app runs with defaults.
  • TS configs are transpiled inline (`typescript.transpile`); `@vesk/compiler` imports are stripped, `defineConfig` / `definePlugin` / `preset` are injected via `globalThis.__vesk_inject`, the result is written to `.vesk/config.tmp.js`, imported, and the exports map makes the helpers available inside the config.
  • The loaded object flows through `defineConfig` (normalization + security defaults) and then `validateConfig` (plugin contract checks) before any command runs.
  • Environment files load before the config (`loadEnvFiles` runs first), so `process.env` is already populated when `vesk.config.ts` evaluates.

Config options

Option Type Description
appDir string Source directory holding routes and components. CLI default: `./app`.
outDir string Build output directory. Default: `resolve(appDir, '..', '.vesk')` — the `.vesk/` folder.
publicDir string Static-assets directory served as-is. CLI default: `./public`.
ssg SSGConfig Static-site generation marker: `{ getStaticPaths?() }`. Pages exporting `getStaticProps` (dynamic pages exporting `getStaticPaths`) pre-render into `.vesk/prerendered/`.
plugins VeskPlugin[] Plugin objects registered into the build/dev pipelines (see the Plugin API page).
security SecurityConfig Preset name, object, `false`, or a function receiving `preset` — see Security below.
routeDataCache number Client-router route-data freshness TTL in ms. Default `0` = always fetch fresh server data on an SPA visit.
md MdConfig Global markdown options for `<Md>`: `html` (`'escape'` default | `'allow'` | `'allowlist'`) and `allowTags`. `'allowlist'` with no `allowTags` uses the built-in default tag list (a, abbr, b, br, code, em, i, kbd, mark, q, s, samp, small, span, strong, sub, sup, time, u, var, wbr, and more).

defineConfig

`defineConfig(config)` returns the config after normalizing it. It resolves `security` (preset string to object, `'off'`/`false` to `{}`, function to its return value) and then fills in guard defaults that apply on top of any preset.

  • `security` may be a named preset (`'strict'`, `'default'`, `'minimal'`), `'off'` / `false`, a plain object, or a function `(preset) => VeskSecurity` that receives the `preset()` helper.
  • Applying the defaults fills: `autoEscape: true`, `csrf: true` (same-origin check for unsafe methods), `xFrameOptions: 'DENY'`, the default Content-Security-Policy, and `redactLogs: true` — even when `security` was `false`/`'off'` (those only suppress the named-preset picks).
  • The default CSP is `default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; frame-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'`.
  • `routeDataCache` defaults to `0` when absent.
  • `md.html` must be one of `escape` / `allow` / `allowlist` (anything else throws a config error); `md.allowTags` entries are lowercased and stripped to `[a-z0-9-]`.

Security presets

The named presets are built in `@vesk/compiler/src/config.ts`. `'default'` is defined as a shallow copy of `'strict'`. Presets only set the fields shown; the rate-limit / CORS / trust-proxy knobs live on `VeskSecurity` but are not part of any preset.

Preset Auto-escape CSRF X-Frame-Options HSTS Content-Security-Policy Referrer-Policy Redact logs
'strict' ✓ ✓ DENY max-age=31536000; includeSubDomains default CSP (lockdown) strict-origin-when-cross-origin ✓
'default' ✓ ✓ DENY max-age=31536000; includeSubDomains default CSP strict-origin-when-cross-origin ✓
'minimal' ✓ — SAMEORIGIN — — — —

`security: false` / `'off'` sets the security object to `{}`. `defineConfig` then still applies its guard defaults (auto-escape, same-origin CSRF, `X-Frame-Options: DENY`, the default CSP, log redaction). `securityHeaders` always emits `X-Content-Type-Options: nosniff` and `X-XSS-Protection: 0` as well.

preset()

`preset(name, overrides?)` returns a security object for `'production'` (strict settings) or `'development'` (strict minus Content-Security-Policy). Unknown names throw, and `overrides` are merged last. It is normally reached through the `security` function form:

vesk.config.ts
									
										import { defineConfig } from '@vesk/compiler'

export default defineConfig({
  security: (preset) =>
    preset('production', {
      contentSecurityPolicy: false,
    }),
})
									
								

Environment variables

  • `vesk` loads `.env` then `.env.local` from the project root before anything else; because keys are only set when not already in `process.env`, `.env.local` wins over `.env`.
  • Only `KEY=VAL` lines are read. Blank lines and `#` comments are skipped; surrounding quotes on values are stripped.
  • Keys already present in `process.env` are never overridden by either file.
  • Loaded before `vesk.config.ts` evaluates, so config files can read `process.env` at load time.

There is no built-in mechanism that copies environment variables into client code. Anything the client needs must be fetched or explicitly wired — do not rely on env vars leaking into the client bundle.

Platforms

`vesk build --platform <name>` picks the deployment artifact. Without the flag, the adapter auto-detects the platform from well-known CI/build environment variables and otherwise defaults to a standard Node server.

Platform How it is selected
node Default when nothing else matches.
vercel `--platform vercel`, or `VERCEL` / `VERCEL_ENV` / `NOW_REGION` / `VERCEL_GIT_COMMIT_SHA` are set.
netlify `--platform netlify`, or `NETLIFY` / `NETLIFY_BUILD_CONTEXT` / `NETLIFY_LOCAL` / `NETLIFY_EDGE` are set.
cloudflare `--platform cloudflare`, or `CF_PAGES` / `CF_PAGES_BRANCH` / `CF_PAGES_URL` / `CLOUDFLARE_WORKERS` / `WORKERS_NAME` are set.
deno `--platform deno`, or `DENO_DEPLOYMENT_ID` / `DENO_REGION` / `DENO_DEPLOY_URL` are set.
aws `--platform aws`, or `AWS_LAMBDA_FUNCTION_NAME` / `AWS_LAMBDA_FUNCTION_VERSION` / `LAMBDA_TASK_ROOT` / `LAMBDA_RUNTIME_DIR` are set.
edge Edge deployment via `--platform edge` (an artifact builder); `--target edge` separately selects the edge runtime target.
coxmos `--platform coxmos`, or `COXMOS` / `COXMOS_DEPLOYMENT_ID` / `COXMOS_ENV` / `VESK_DEPLOY` / `VESK_PLATFORM=coxmos` are set.

App events and server context

App-level lifecycle events live in one convention file: `app/_events.ts` (or `app/_events.js`), which exports optional `onStart`, `onRequest` and `onStop` handlers. The `_` prefix marks the file private — it is never routed. Both the events file and plugin `onStart`/`onStop` hooks receive a `ServerEventContext`.

app/_events.ts
									
										import type { ServerEventContext } from '@vesk/types'

export async function onStart(ctx: ServerEventContext) {
  ctx.set('bootedAt', Date.now())
}

export async function onRequest(ctx: ServerEventContext) {
  const hits = (ctx.get('hits') as number) || 0
  ctx.set('hits', hits + 1)
}
									
								
  • `onStart` runs once when a dev/start server boots, before it begins listening; on serverless/edge targets it runs lazily once per isolate. Node servers additionally expose `server`, `port`, and `host`.
  • `onRequest` runs for every request and receives the per-request `request`, `params`, `url`, `cookies` and `locals` in addition to the shared store.
  • `onStop` runs on graceful shutdown (`SIGINT`/`SIGTERM`). Node dev/start servers only — serverless/edge targets have no shutdown lifecycle.
  • `ctx.set(key, value)` / `ctx.get(key)` read and write the process-wide store exposed as `serverLocals`. Values set during `onStart` are pre-seeded into every request's `locals()`.
  • Dev servers run the live handlers and reload them on HMR; production builds bake `app/_events.ts` into `server/events.js` at build time.
Field Description
server The running Node `http.Server`, or `null` on serverless/edge targets and at build time.
port / host The port (0 without a persistent server) and bind address.
request / params / url / cookies Per-request values, populated for `onRequest`.
locals Per-request context, pre-seeded from the server-wide store.
serverLocals The process/isolate-wide store shared across every request.
set(key, value) / get(key) Write / read the server-wide store.

The plugin `onStart` and `onStop` hooks receive the same `ServerEventContext` — see the Plugin API page. Plugin objects only exist in dev/build processes; the production process runs the baked `app/_events.ts` handlers instead.

Component context (createContext)

`createContext` is a component-scoped runtime API (from `@vesk/runtime/src/context.ts`), not a config or plugin feature. It is auto-imported from `@vesk/runtime` whenever a `.vsk` component uses it. `createContext(defaultValue)` returns a `Context<T>`: `get()` walks up the active-component chain returning the nearest value set by an ancestor (falling back to the default), and `set(value)` stores a value for the currently rendering component (it throws `No active component found, cannot set context` outside a render). `getActiveComponent()` / `setActiveComponent()` are the runtime's lower-level hooks for tracking the active component and are used by renderer internals.

theme.vsk
										
											component Theme {
  const Theme = createContext('light')
  Theme.set('dark')
  <p>{Theme.get()}</p>
}
										
									

Component context (`createContext` / `Context`) is per-component-tree state and is unrelated to the server-wide `serverLocals` store above, and to the plugin `provides` injection mechanism.