Project Structure
Use this page to decide who owns a file, where it runs, and which artifacts tools generate. Complete Getting Started first. Paths below reflect the default TypeScript full-stack template; see Frontend Configuration for configurable paths.
Table of Contents
- Default Layout
- Frontend Source Boundary
- Type Boundaries
- Generated Files
- Aliases
- Static Files
- API-only Projects
- Verify Directory Changes
Default Layout
npx vextjs create my-app creates a TypeScript full-stack project by default. This is its main source layout; environment and bootstrap configuration files also appear under config, as explained in Server Project Structure.
Add business files as needed; the template does not create every directory below in advance:
Put URL-addressed files under public/**. Supported images and fonts in src/frontend/assets/** can be imported by pages and components; SSR and browser output share the artifact URL. Inlining and hashing depend on build settings.
Frontend Source Boundary
Server and browser files are intentionally separate.
Do not import src/services/**, database clients, secrets, node:*, or route handlers from src/frontend/**. The default frontend.build.diagnostics.leakScan checks known server paths and Node module boundaries; it does not detect all private data or third-party side effects. Inspect real dependencies even in a shared directory. Disabling the scan does not make server code browser-safe.
The page/component runtime locations above assume SSR and hydration are enabled; disabling either changes the corresponding execution stage. During SSR, do not access window or document unconditionally. pages/_document.html is a document template, not a React page component.
Type Boundaries
The TypeScript full-stack starter makes type ownership visible without creating a second backend tree:
The main vext typegen declarations are .vext/types/services.generated.d.ts and .vext/types/app-extensions.generated.d.ts. In TypeScript projects, src/types/generated/index.d.ts references them. Command options select which declarations are written; --write-manifest also writes .vext/manifest/services.json. Tooling does not rewrite application-owned shared/** or frontend/**, and placing a type there does not automatically make its data serializable.
The scaffold does not reserve src/types/server/**. Keep a server-only type next to its route or service owner; introduce an application-specific server folder only after you have a real shared server boundary.
Generated Files
The frontend build generates browser and SSR entries and registries. These are the main artifacts in the default layout; optional features add files, so this is not a complete deployment inventory.
Application maintainers edit their routes, services, UI, configuration, types under src, and public assets. Do not hand-edit .vext/generated/frontend/** or generated declarations. Pages, layouts, and error pages are registered in page-registry.ts. When frontend i18n is enabled, dictionaries are scanned into that same registry. Separate layout-registry.ts and locale-registry.ts are not generated.
Frontend development output defaults to .vext/client/, production output to dist/client/; frontend.outDir may override it. See the Build guide for how CLI --outdir interacts with this setting. Deliver the SSR renderer and browser assets together; uploading only assets/ does not publish server-rendered pages.
Aliases
The frontend resolver provides these default aliases:
This imports the component created in Getting Started. When you change frontend.root, pages.dir, componentsDir, assetsDir, or styles.entry, default aliases follow the resolved directories; @styles points to the style-entry directory. Custom frontend.alias values resolve relative to the frontend root and may override a default. Keep TypeScript editor paths in tsconfig.json aligned; the runtime resolver does not rewrite that configuration.
root, publicDir, and entry paths are relative to the project root, while page, component, style, and asset paths are usually relative to the frontend root. Check each field in Frontend Configuration before moving directories.
Static Files
Under the default publicPath: "/", the template's existing public/favicon.svg can be referenced by this embeddable component:
To display the template mark, reference its existing Public asset as well:
This example uses a stable Public URL. Supported PNG, SVG, and other image/font imports can also be used directly. SSR reuses verified browser URLs, including inline data URLs and CDN prefixes, without editing generated files.
For an existing browser-only entry where importing an asset is supported, a missing TypeScript module declaration can be added in an application-owned type directory:
The declaration only helps type checking. The file must exist and use a supported resource format. See Styles and Assets for formats, CSS Modules, and media handling.
public/** is copied into frontend output and its static asset inventory. Do not place server configuration or other private files there. Local development and production servers serve it under the configured publicPath; see Static Assets and CDN for CDN rewriting.
API-only Projects
Disable frontend at creation time:
For an existing project, merge this configuration fragment into the default config:
Both frontend: false and { enabled: false } disable built-in frontend build and static/page handling. Existing routes that call res.render() must be changed to API responses or removed, or they will report that frontend is disabled. Changing configuration does not automatically delete old build directories from disk.
Verify Directory Changes
Run these existing commands in the application root:
typegen --check checks that generated files match current source and runs its own diagnostics; it does not replace TypeScript type checking. The default TypeScript template's npm run build includes --typecheck; inspect a custom build script separately. Confirm a strict build passes and the adjusted page/imported resources are in this build, then start the app and request the changed URLs and assets. For an editor-only alias error, inspect tsconfig paths. For a browser boundary leak, inspect the UI's entire import chain. For a missing page, check its ID and pages.dir rather than editing a generated registry. Continue with Getting Started for a full running example.