Frontend Configuration

This page is a decision guide, not a second copy of every type member. Start with the defaults, configure only the behavior that changes for your product, and use the canonical VextFrontendConfig API reference when you need an exact field, default, or nested option.

Table of Contents

Choose What to Configure

If you need…Start withConfigureWhat changesVerify
Server-rendered React pagesfrontend: trueNothing elseVext discovers src/frontend, builds browser + SSR output, and serves both at the application originvext build, then vext start
A different source layoutBuilt-in foldersroot, pages, componentsDir, styles.entry, or assetsDirOnly discovery paths change; generated entries remain Vext-ownedBuild and load one page plus its global style
A browser-size or compatibility targetProduction defaultsbuild, vendorChunks, or budgetsesbuild output, report thresholds, or browser support changesInspect size-report.json and a production page
CDN-hosted immutable assetsSame-origin deliverydeploy.assetBaseUrl and optionally deploy.uploadGenerated JS/CSS URLs point at the CDN; Node still owns HTML/SSRDry-run the upload and request an SSR page + hashed asset
Search-visible public pagesSEO disabledseo, plus route/render metadataCanonical/meta output and optional sitemap/robots become framework-ownedInspect two page canonicals and the selected SEO artifacts
A client-router islandNo fallback capturespaFallback.scopesOnly declared paths are served by the browser shellCheck an in-scope URL and an excluded /api/** URL
Localized page copyDisabledi18nLocale artifacts and request-aware document language are generatedBuild and request two locales

Avoid adding a field merely because it exists. The defaults deliberately keep the runtime small: React + esbuild, SSR on, buffered streaming, browser code splitting on, production browser minification on, and no CDN/upload adapter.

Minimal Config

export default {
  frontend: true,
};

Use false to disable frontend completely:

export default {
  frontend: false,
};

frontend: true uses src/frontend, pages, components, styles/index.css, and public conventions. It creates dist/client in a production build; browser minification is enabled and browser source maps are disabled by default. The SSR renderer is a separate Node bundle and stays unminified by default for diagnostics.

Complete Example

export default {
  frontend: {
    enabled: true,
    framework: "react",
    root: "src/frontend",
    publicDir: "public",
    publicPath: "/",
    styles: {
      jscss: { enabled: true },
    },
    dev: {
      hot: true,
      fastRefresh: true,
      renderRefresh: "prompt",
    },
    build: {
      target: "es2022",
      minify: true,
      sourcemap: false,
      client: {
        external: [],
        externalRuntime: {},
      },
      vendorChunks: {
        enabled: true,
        packages: ["react", "react-dom", "react-dom/client"],
      },
      assets: {
        inlineLimit: 0,
      },
      css: {
        modules: true,
      },
      budgets: {
        maxInitialJsBrotliBytes: 60_000,
        maxRouteInitialJsBrotliBytes: 80_000,
        maxAppOwnedInitialJsBrotliBytes: 40_000,
      },
      diagnostics: {
        leakScan: true,
        performanceReport: true,
      },
    },
    deploy: {
      assetBaseUrl: "https://cdn.example.com/my-app/",
      crossOrigin: "anonymous",
      integrity: true,
      upload: {
        enabled: true,
        adapter: "filesystem",
        targetDir: ".vext/frontend-cdn",
        publicBaseUrl: "https://cdn.example.com/my-app/",
        prefix: "my-app",
        stateFile: ".vext/deploy/frontend-assets-state.json",
        exclude: ["**/*.map"],
      },
    },
    i18n: {
      enabled: true,
      defaultLocale: "en-US",
      clientLoad: "current",
    },
    spaFallback: {
      scopes: [],
    },
    apiClient: true,
  },
};

Production Delivery Profiles

Same-origin (default)

Do not configure a CDN for the first production deployment:

export default {
  frontend: true,
};

vext build writes the frontend closure to dist/client; vext start validates it and serves assets plus SSR from the same Node service. This is the baseline to keep when a separate static origin provides no material value.

CDN plus incremental upload

Add only the delivery fields required by the CDN path:

export default {
  frontend: {
    deploy: {
      assetBaseUrl: "https://cdn.example.com/my-app/",
      integrity: true,
      upload: {
        enabled: true,
        adapter: "filesystem",
        targetDir: ".vext/frontend-cdn",
        stateFile: ".vext/deploy/frontend-assets-state.json",
        exclude: ["**/*.map"],
      },
    },
  },
};

filesystem only stages a deploy tree. Use a custom adapter for a real provider; no cloud SDK or bundler-plugin ecosystem is implicitly installed. Keep the state file outside frontend.outDir, run vext deploy assets --dry-run, then deploy the matching Node dist/ output.

Core Fields

FieldDefaultMeaning
frontend.enabledfalseEnable built-in frontend pipeline
frontend.framework"react"Framework label for built-in React support
frontend.root"src/frontend"User frontend source root
frontend.pagesBuilt-in page conventionsPage, document, and error-page discovery settings
frontend.componentsDir"components"Shared component directory resolved from frontend.root
frontend.assetsDir"assets"Imported image, font, and media source directory
frontend.indexHtmlsrc/frontend/pages/_document.htmlDocument template
frontend.outDir.vext/client in dev, dist/client in buildFrontend output directory
frontend.publicDir"public"Static public directory
frontend.publicPath"/"Public asset URL prefix
frontend.aliasBuilt-in @frontend/@pages/@components/@styles/@assetsFrontend-safe import aliases; do not alias all of src into browser code
frontend.apiClienttrueEmit route/client contract artifacts; set false only when no generated client artifact is wanted
frontend.errorPagesBuilt-in error page conventionsMap default or status-specific SSR errors to pages
frontend.adapternoneAdvanced compatible adapter seam; not a general plugin loader

Style Fields

FieldDefaultMeaning
frontend.styles.entrystyles/index.cssGlobal CSS entry resolved from frontend.root
frontend.styles.jscss.enabledtrueEnable Vext JSCSS extraction
frontend.styles.jscss.files**/*.style.ts, **/*.style.js, **/*.css.tsJSCSS source globs
frontend.styles.jscss.runtimeAdaptercss-variablesEmit dynamic variables as CSS custom properties; none/false uses fallback values
frontend.styles.jscss.dynamicVarstrueEmit custom property declarations and var(...) references
frontend.styles.jscss.recipestrueEmit recipe variant classes and rules

Build Fields

FieldDefaultMeaning
frontend.build.target"es2022"Default browser target passed to esbuild; build.client.target overrides it
frontend.build.minifyproduction trueMinify browser output; distinct from the server renderer setting
frontend.build.sourcemapdev trueEmit browser source maps; production defaults to false
frontend.build.client.assetsDir"assets"Browser bundle asset subdirectory
frontend.build.client.entryNames / chunkNames / assetNames"[name]-[hash]"Hashed filename patterns; preserve hashing for immutable caching
frontend.build.client.splittingtrueEnable browser code splitting
frontend.build.client.external[]Browser external modules
frontend.build.client.externalRuntime{}Import-map URLs for browser externals
frontend.build.server.outFileserver/renderer.cjsSSR renderer bundle file under frontend.outDir; its default minify setting is false
frontend.build.vendorChunksenabledShared runtime chunk strategy; configure packages only for a measured reason
frontend.build.budgetsall limits 0Enforce raw/gzip/brotli budget thresholds; use warnOnly while baselines settle
frontend.build.assets.inlineLimit0Inline imported assets below this byte size
frontend.build.css.modulestrueEnable CSS Modules
frontend.build.diagnostics.leakScantrueBlock server-only imports from browser graph
frontend.build.diagnostics.sizeReporttrueWrite size-report.json
frontend.build.diagnostics.performanceReporttrueInclude route-level performance metrics

React-related browser externals must define externalRuntime mappings. Otherwise the build fails with a friendly diagnostic.

Browser output is directory-based and uses frontend.outDir; frontend.build.client.outFile is not supported. Vext always emits the frontend manifest family required by SSR, preload, deploy, and verification, so build.client.manifest / build.server.manifest are not configuration fields.

For a normal product, keep browser code splitting, hashed names, and the Vext-managed vendor entry enabled. Start with budgets as warnings, inspect the complete route closure in size-report.json, and only then turn the budget into a release-blocking gate.

Deploy Fields

FieldDefaultMeaning
frontend.deploy.assetBaseUrlnoneCDN/public base URL for assets
frontend.deploy.crossOriginnonecrossorigin value for generated tags
frontend.deploy.integrityfalseAdd SRI integrity for generated JS/CSS
frontend.deploy.upload.enabledfalseEnable vext build --upload-assets / vext deploy assets upload
frontend.deploy.upload.adapter"filesystem"filesystem, mock, or custom adapter
frontend.deploy.upload.targetDirenabled: .vext/deploy/frontend-assetsLocal staging destination for filesystem
frontend.deploy.upload.publicBaseUrlnoneOptional public URL reported by upload; filesystem falls back to assetBaseUrl
frontend.deploy.upload.prefix / concurrency"" / 4Upload key namespace and parallelism
frontend.deploy.upload.stateFile.vext/deploy/frontend-assets-state.jsonIncremental upload state
frontend.deploy.upload.exclude["**/*.map"]Files excluded from upload

assetBaseUrl must be an absolute URL. deploy-manifest.json uploads JS, CSS, imported media, and copied public files; it does not upload SSR HTML or source maps by default. Use vext deploy assets --dry-run before every new adapter, prefix, or include/exclude rule.

SEO Fields

frontend.seo is the framework-level SEO entry point. It is disabled when omitted; when the object is present, enabled defaults to true.

frontend: {
  seo: {
    publicOrigin: process.env.PUBLIC_ORIGIN ?? "https://www.example.com",
    titleTemplate: "%s | Example",
    defaults: { description: "Example application" },
    sitemap: {},
    robots: {},
  },
}

publicOrigin identifies the deployment origin. Vext combines it with each request pathname, so dynamic pages do not share one fixed URL. Use route-level frontend.seo for static metadata and res.render(..., { seo }) for metadata derived from page data. sitemap and robots can use "build" or "runtime" mode; named origins support a finite multi-domain deployment.

See SEO, Sitemap, and Robots for dynamic canonical, provider, host-selection, output, and no-hydration examples. The exact nested field list is in the API reference.

I18n Fields

FieldDefaultMeaning
frontend.i18n.enabledfalseScan and bundle frontend page copy when explicitly enabled
frontend.i18n.sourcelocalesLocale source directory resolved from frontend.root
frontend.i18n.defaultLocale"inherit"Fallback frontend locale; inherits request locale by default
frontend.i18n.detect / injectaccept-language / usedSSR locale detection and message injection policy
frontend.i18n.clientLoad"current"Browser locale loading mode
frontend.i18n.clientSwitch"reload"Browser behavior when the selected locale changes
frontend.i18n.htmlLangtrueWrite request-aware {vext.lang} / <html lang>

Dev Fields

FieldDefaultMeaning
frontend.dev.hottrueEnable frontend dev events
frontend.dev.fastRefreshtrueEnable React Fast Refresh when possible
frontend.dev.transport"sse"Vext development event-bus transport; this is not a user-selectable WebSocket mode
frontend.dev.overlaytrueShow browser UI for frontend rebuild errors and render refresh prompts
frontend.dev.debounceMs50Coalesce rapid file-system changes before a rebuild
frontend.dev.renderRefresh"prompt"Browser behavior after render-data backend reload

frontend.dev.overlay only controls frontend browser development UI. Backend exception HTML overlays are configured separately through top-level dev.errorOverlay.

SPA Fallback Fields

FieldDefaultMeaning
frontend.spaFallback.enabledtrueEnables arbitration only; with no scopes it captures no page
frontend.spaFallback.scopes[]Explicit client-router sub-app fallback scopes
frontend.spaFallback.exclude["/api/**", "/openapi.json", "/docs/**", "/_vext/docs/**"]Global paths that fallback never captures
scopes[].basePathrequiredURL prefix handled by the shell
scopes[].pagerequiredShell page id from src/frontend/pages/**
scopes[].ssrfalseWhether the shell should be SSR-rendered
scopes[].exclude[]Paths that must not be handled by fallback
scopes[].status200HTTP status for matched fallback

Declare individual scopes instead of a site-wide catch-all. API, OpenAPI, and documentation routes stay excluded by default so a client-router shell cannot hide an operational endpoint.

Verify a Configuration Change

# Compile the backend, browser, and SSR closure.
vext build

# Required only when configuring an upload path; inspect before writing.
vext deploy assets --dry-run

# Verify the production closure and start the Node runtime.
vext start

For a build or budget change, inspect dist/client/size-report.json. For a CDN change, request one SSR page and one hashed browser asset and confirm they belong to the same release. For SPA fallback, also request a deliberately excluded API path. The API reference is the canonical source for less-common nested fields.