Build
Command
ev build
ev build resolves config, creates an AppGraph, derives a BuildPlan, runs the selected bundler, links a single BuildOutput, and emits HTML.
Use ev inspect when you need to explain what evjs discovers before bundling:
ev inspect
ev inspect --json
ev inspect resolves config and framework declarations, but it does not run a
bundler and does not write dist. It reports routing mode, discovered page
routes, ignored or rejected route files, generated route type location, server
functions, server routes, page render metadata, remotes, runtime server paths,
planned entries/documents, and diagnostics. If any diagnostic is an error, the
command exits non-zero; warnings are printed without failing the command.
Output
Fullstack output:
dist/
├── client/
│ ├── index.html
│ ├── main.[hash].js
│ └── [chunk].[hash].js
├── server/
│ └── server.[hash].js
└── manifest.json
CSR-only output (server: false) is flat:
dist/
├── index.html
├── main.[hash].js
├── [chunk].[hash].js
└── manifest.json
dist/manifest.json is the framework contract consumed by runtime, server,
shell, and deployment adapters. HTML may embed this manifest as
__EVJS_MANIFEST__; when the browser runtime fetches it through manifestUrl,
data-evjs-manifest, or /manifest.json, the response must be successful JSON
with Content-Type: application/json, allowing optional content-type
parameters.
Build Pipeline
- Load and resolve
ev.config.ts. - Run config/setup plugin hooks.
createAppGraph()analyzes the file-based page route files, lower-level app/page outputs, server entry, and remotes.createBuildPlan()produces concrete client/server entries and HTML documents.- The selected bundler compiles
BuildPlan.entries. linkBuildOutput()combinesAppGraph,BuildPlan, and bundler facts.- evjs emits
dist/manifest.json. - evjs generates each planned HTML document and calls
transformHtml(doc, ctx). - evjs calls
buildEnd({ output, isRebuild }).
Manifest linking does not rescan user source after bundling.
Programmatic Preparation
Tools that need framework semantics without invoking a bundler can call
prepareFrameworkBuild() from @evjs/ev. It resolves config, applies
page-routing defaults, initializes plugins, runs buildStart hooks, reports
graph diagnostics, and returns the resolved config, graph file dependencies,
plugin watch files, and dispose(). AppGraph and BuildPlan remain internal
framework state.
The preparation API stops before bundler execution, manifest emission, HTML emission, and deployment adapter output.
For a CLI preflight with human-readable diagnostics, prefer ev inspect. It
uses the same graph and plan preparation path, while keeping AppGraph and
BuildPlan as framework internals.
Server Functions
Files with "use server" are transformed into browser-callable references and server registrations:
| Side | What happens |
|---|---|
| Client | Function bodies are replaced with internal RPC stubs |
| Server | Function implementations are registered for framework server dispatch |
Function output is recorded in BuildOutput.server.functions. Its object keys
are server function ids: non-empty strings without leading or trailing
whitespace. They are not build identifiers, so generated ids can use separators
such as fn:refund. The public endpoint is derived from server.basePath:
server.basePath = /__evjs
runtime.server.fn = /__evjs/fn
Framework Pages
File-based routes and configured component pages both become framework-managed
component pages. The lower-level pages string shorthand means "component
page"; { entry } pages compile as user-owned client entries for cases that
cannot use the page-file convention. Component pages add explicit metadata so a
bundler adapter can wrap the real component import with the generic page
runtime. The BuildPlan.import remains the user component path; evjs does not
write hidden production source files.
SSR/PPR pages add server render entries to the plan. PPR pages produce a shell
renderer and one renderer per React Suspense boundary whose direct child is
lazy(() => import(...)). At runtime the framework server resolves those
regions while serving the page route, so the initial browser navigation stays
one document request. PPR supports two document delivery modes:
mergeis the default non-streaming mode. The server waits for resolved regions and returns a complete HTML response.streamsends the shell first, then sends region patches in the same HTML response as each region resolves.
PPR component pages do not create a page-level browser entry. Their public
manifest hydration mode is none until explicit client islands or region-level
hydration are modeled.
File-route pages that export render = "ssg" keep the same route-owned document
contract in SPA mode: the app HTML fallback is still the only planned SPA
document, while the page records rendering.html = "static" and gets a server
renderer for static generation/deployment adapters. Use a configured component
page without path when you need a standalone static HTML file such as
pricing.html.
In MPA mode, a file-route page that exports render = "ssg" keeps the MPA
document contract: it emits its own static HTML document, such as
pricing.html, and a server renderer for static generation. It does not create
a browser page entry unless the page opts into hydration.
MPA file-route pages that export render = "ssr" are route-owned server
documents instead: they get a page-server renderer and, when hydrated, a
page-level browser entry, but no static HTML file is emitted.
PPR regions carry cache metadata in the manifest:
{
"pages": {
"campaign": {
"render": "ssr",
"rendering": {
"component": "server",
"html": "partial",
"prerender": "partial",
"streaming": false,
"hydrate": "none"
},
"ppr": {
"delivery": "stream",
"regions": {
"inventory": {
"cache": { "revalidate": 60 }
}
}
}
}
}
}
Key Points
- One framework manifest:
dist/manifest.json. BuildOutputis the framework manifest contract.- Manifest object keys that become runtime ids, including app ids, page ids, and PPR region ids, must be build identifiers: letters, numbers, underscores, or hyphens.
- App and page runtime modules must link to a JavaScript asset; manifest emission fails if a client entry only produced CSS or no assets.
- Server-enabled builds must link the server runtime entry to a JavaScript
asset; deployment adapters rely on
server.entryto import the framework handler. - Build entry names are manifest asset keys. They must be build identifiers and must be globally unique across app, page, remote, runtime, and server entries.
manifest.server.rendererskeys are renderer build entry names and must use the same build-identifier rule.- In full server manifests, each SSR, SSG, or RSC document page with server
HTML must have a
page-serverrenderer owned by that page id, or by a route id whosemanifest.routesentry points to that page. PPR pages use theirppr-shellandppr-regionrenderer references instead. manifest.routesids must be unique non-empty strings without leading or trailing whitespace. Page route paths must keep one entry per normalized URL path and dynamic URL shape;pageIdandappIdmust point to existing manifest pages or apps.- RSC reference maps are not build-identifier keyed: reference ids may contain
file paths, URLs, hashes, or server-function punctuation. They still must use
non-empty trimmed string keys, and each value must be an object with a
non-empty trimmed
moduleand optional non-empty trimmedexportName. BuildOutput.rsc.endpointmay be omitted when the RSC section only carries reference metadata. It is required as soon asBuildOutput.rsc.pagescontains Flight-rendered pages; manifest emission fails before writing an RSC page output that has noruntime.server.rscendpoint.- In full server manifests, each
BuildOutput.rsc.pages[id].renderermust point to anrsc-pageserver renderer owned by the same page id. Public manifests may omit server renderer metadata because it is redacted. BuildOutput.server.routesmust keep one entry per URL path and dynamic URL shape. Dynamic params must be named safely and uniquely inside one route path.- The public manifest is redacted: browser-visible output must not expose local source paths or private build metadata.
- Public manifest validation uses the same structural contract, but treats server-only metadata such as source modules and server renderer references as optional because those fields are intentionally redacted.
- Source analysis happens before bundler config creation and is cached in dev.
- Component/style edits stay in the bundler HMR path.
- The default Utoopack adapter can relink HTML-only dev plan updates from existing build stats. Adding or removing configured page entries in dev still requires a restart until Utoopack exposes a lower-layer entry update API.