5.8 · difficulty 4/4 · 12 min read
`buildApp` Hook in Practice
Use the buildApp hook introduced in Vite 7+ to coordinate multi-environment build order — ssr before client, or parallel builds — and how to share build information across environments.
Vite 8.1RC
What is buildApp
buildApp is a plugin hook introduced in Vite 7 and matured further in Vite 8, designed specifically for orchestrating multi-environment builds.
Before buildApp, if your project had three environments (client / ssr / rsc), you had to run them separately:
bash
vite build # build client
vite build --ssr # build ssr
# rsc had no standard approach — each framework implemented its ownbuildApp lets you describe this orchestration in code:
ts
import type { Plugin } from "vite"
export function myFrameworkPlugin(): Plugin {
return {
name: "my-framework",
async buildApp(app) {
// Build in order: ssr first, then client (client may depend on ssr output)
await app.build("ssr")
await app.build("client")
},
}
}Typical scenario: sequential SSR + Client build
Many SSR frameworks need to build the server-side code before the client-side code. The reason:
- The SSR output contains a route manifest
- The client needs to read the route manifest to perform code splitting
ts
async buildApp(app) {
// Step 1: build the SSR environment
await app.build("ssr")
// SSR output is now in dist/server/
// Read the route manifest from the SSR output
const manifest = readRouteManifest("dist/server/route-manifest.json")
// Step 2: build the client with the route manifest
// (some mechanism is needed to pass the manifest to the client build)
await app.build("client")
}Typical scenario: parallel builds for independent environments
If two environments have no dependency on each other, they can be built in parallel:
ts
async buildApp(app) {
// client and worker are independent — build in parallel
await Promise.all([
app.build("client"),
app.build("worker"),
])
}Framework perspective: a Next.js-like build flow
ts
async buildApp(app) {
// 1. Build the RSC environment (React Server Components)
await app.build("rsc")
// 2. Read the server component manifest from the RSC build
const rscManifest = readManifest("dist/rsc/manifest.json")
// 3. Pass the manifest into the ssr build
await app.build("ssr")
// 4. Finally build the client (requires complete SSR + RSC manifests)
await app.build("client")
}Sharing information inside buildApp
ts
async buildApp(app) {
const sharedData = new Map<string, unknown>()
// After the ssr build completes, collect information
await app.build("ssr")
sharedData.set("routeManifest", readManifest())
// During the client build, plugins can access sharedData
// (passed via closure or another mechanism, depending on the framework)
await app.build("client")
}Self-check
- Is the
buildApphook primarily for application developers or framework authors? Why? - When is
await Promise.all([app.build("a"), app.build("b")])appropriate versusawait app.build("a"); await app.build("b")? - Why do some frameworks need to build SSR before client, rather than the other way around?
- If the
buildApphook did not exist, how would a framework implement multi-environment build orchestration?
ts
// Implement buildApp for a hypothetical SSR framework:
// Environment structure:
// - ssr: server-side rendering entry, builds to dist/server/
// - client: browser code, builds to dist/client/
// - edge: optional Edge Middleware, builds to dist/edge/
//
// Build rules:
// - ssr must build before client (client needs ssr's manifest)
// - edge can build in parallel with client
// - if no edge environment is configured, skip the edge build
import type { Plugin } from "vite"
export function frameworkPlugin(): Plugin {
return {
name: "my-framework",
async buildApp(app) {
// TODO: implement the build order described above
// Hint: app.environments contains all configured environments
// app.build(envName) builds the specified environment
},
}
}