withastro/astro
Architecture
Astro is split into three execution contexts. The same TypeScript source tree compiles into modules that run in Node, in Vite (during dev or build), and in the browser. Confusing the boundaries is the most common source of subtle bugs in this codebase.
The three contexts
graph TD
subgraph Node["Node.js (packages/astro/src/core/)"]
CLI["bin/astro.mjs<br/>src/cli/"]
Builder["AstroBuilder<br/>src/core/build/"]
DevServer["DevPipeline + Vite<br/>src/core/dev/, src/vite-plugin-astro-server/"]
AppNode["App entrypoints<br/>src/core/app/"]
end
subgraph Vite["Vite SSR (src/runtime/server/)"]
Renderer["renderToString / streaming<br/>render-context.ts"]
Compiler["@astrojs/compiler runtime<br/>src/runtime/compiler/"]
end
subgraph Browser["Browser (src/runtime/client/)"]
Hydration["client:* directives<br/>idle / load / media / only / visible"]
Toolbar["Dev toolbar UI<br/>src/runtime/client/dev-toolbar/"]
Transitions["View transitions router<br/>src/transitions/router.ts"]
end
CLI --> Builder
CLI --> DevServer
DevServer --> Renderer
Builder --> Renderer
AppNode --> Renderer
Renderer -->|emits HTML + island scripts| Browser
Renderer --> Compilerpackages/astro/src/core/ contains everything that runs in Node directly. packages/astro/src/runtime/server/ runs inside Vite's SSR module graph and must avoid raw Node APIs unless explicitly gated. packages/astro/src/runtime/client/ is shipped to browsers and is restricted to browser-compatible code.
The repo uses a runtime/ naming convention to mark code that may run in non-Node runtimes (Cloudflare Workers, Deno). See packages/astro/CONTRIBUTING.md and the "Naming convention and APIs usage" section of the project CONTRIBUTING.md.
The pipeline abstraction
The Pipeline is the cross-cutting object that holds everything that does not change for the duration of a server or build: the user config, the route manifest, the logger, the way to gather scripts/styles for a given route, and the page renderers. There are three implementations:
| Pipeline | When it runs | Defined in |
|---|---|---|
DevPipeline |
astro dev (long-running Vite server) |
packages/astro/src/vite-plugin-astro-server/pipeline.ts |
BuildPipeline |
astro build static and prerender phases |
packages/astro/src/core/build/pipeline.ts |
AppPipeline |
Production runtime (used by adapters) | packages/astro/src/core/app/pipeline.ts |
All three implement the abstract Pipeline from packages/astro/src/core/base-pipeline.ts. They are consumed by RenderContext (packages/astro/src/core/render-context.ts) which is per-request — it parses the URL, runs middleware, executes the matched route, and produces the Response.
graph LR
subgraph PerProcess["Per-process (Pipeline)"]
Config[AstroConfig]
Manifest[Route manifest]
Logger[AstroLogger]
Renderers[JSX renderers]
end
subgraph PerRequest["Per-request (RenderContext)"]
URL[Parsed URL]
Locals[locals + cookies]
Middleware[middleware chain]
Match[matched RouteData]
end
Config --> Match
Manifest --> Match
Match -->|route + props| Render[Render page or endpoint]
Middleware --> Render
Locals --> Render
Render -->|Response| Caller[Adapter / Vite middleware]Command lifecycles
astro dev
packages/astro/src/cli/dev/index.ts calls dev() from packages/astro/src/core/dev/dev.ts. That spins up a Vite dev server through createContainerWithAutomaticRestart (packages/astro/src/core/dev/restart.ts) and registers the long stack of vite-plugin-* plugins from packages/astro/src/. The actual SSR-on-each-request lives in packages/astro/src/vite-plugin-astro-server/, which builds a DevPipeline and a RenderContext per request.
astro build
AstroBuilder (packages/astro/src/core/build/index.ts) orchestrates the build:
- Run a Vite SSR build to compile every page module.
- Collect emitted CSS/JS via the SSR plugins in
packages/astro/src/core/build/plugins/. - Walk the route manifest and call
BuildPipelineto render every static page. - For SSR output, emit an adapter entrypoint and a serialized manifest (
packages/astro/src/core/app/manifest.ts). - For server islands, emit a per-island
_actions/_actions.jsonstyle endpoint.
packages/astro/src/core/build/static-build.ts is the largest single file in core/build/ and drives steps 1–3.
Production runtime
When an adapter (@astrojs/node, @astrojs/vercel, …) starts, it constructs an App from the serialized SSRManifest. App.render(req) builds an AppPipeline once and a RenderContext per request. The same RenderContext runs in dev and prod, which is why most regressions in routing/middleware/i18n affect both modes.
Vite plugin layering
Astro's "core framework" is in many respects a stack of Vite plugins. The plugins under packages/astro/src/ named vite-plugin-* cover, in rough order of loading:
| Plugin | Responsibility |
|---|---|
vite-plugin-environment |
Wires up optimizeDeps.entries so dependencies reachable only through .astro files get pre-bundled (see reference/optimize-deps). |
vite-plugin-astro |
Compiles .astro files via @astrojs/compiler. |
vite-plugin-astro-server |
The dev-server SSR middleware. Builds a DevPipeline/RenderContext per request. |
vite-plugin-html |
Loads .html routes. |
vite-plugin-markdown |
Loads .md routes via @astrojs/markdown-remark. |
vite-plugin-pages, vite-plugin-routes |
Generate the route manifest from the src/pages/ directory. |
vite-plugin-renderers |
Injects framework client/server renderer modules. |
vite-plugin-css |
CSS handling and ordering. |
vite-plugin-config-alias |
Alias resolution from tsconfig paths. |
vite-plugin-fileurl |
Treats ?fileurl imports as URL strings. |
vite-plugin-head, vite-plugin-scripts |
Head and <script> propagation. |
vite-plugin-fonts (under assets/) |
The font subsystem. |
vite-plugin-content-imports, vite-plugin-content-virtual-mod, vite-plugin-content-assets (under content/) |
Content collections support. |
vite-plugin-server-islands (under core/server-islands/) |
Server islands codegen. |
vite-plugin-actions (under actions/) |
Type-safe form/data actions. |
vite-plugin-i18n (under i18n/) |
i18n virtual modules and middleware. |
vite-plugin-prefetch (under prefetch/) |
Built-in prefetch script. |
vite-plugin-transitions (under transitions/) |
View transitions support. |
vite-plugin-app |
Glue for the App entrypoint. |
vite-plugin-load-fallback |
Last-resort module resolver for virtual modules. |
packages/astro/src/core/create-vite.ts is where the Vite config is assembled and the plugin order is decided. That file is one of the most-touched in the codebase.
Public exports of astro
The astro package exposes a deliberately layered API. The entrypoints in packages/astro/package.json are split into two views:
"exports"is the monorepo view — includes./_internal/*subpaths consumed by sibling packages."publishConfig.exports"is the npm view — pnpm rewrites"exports"to this at publish time, so_internal/*never ships.
Notable public entry points:
astro—dev,build,preview,syncJS API (packages/astro/src/index.ts→core/index.ts).astro/app,astro/app/node,astro/app/manifest— the runtime adapters use to construct anApp.astro/container— programmatic component rendering (packages/astro/src/container/).astro/middleware,astro/loaders,astro/content/runtime,astro/assets,astro/jsx-runtime,astro/zod,astro/errors,astro/components,astro/toolbar.astro/runtime/*— the JS that ships to the browser (idle.js,load.js,media.js,only.js,visible.js).
packages/astro/test/exports.test.ts enforces that the two exports maps stay in sync after stripping _internal/*.
Related pages
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.