.agents/skills/astro-developer/architecture.md
Understanding Astro's internal architecture is critical for effective development. This guide covers execution contexts, pipelines, virtual modules, and content layer architecture.
Astro code runs in three distinct environments. Understanding which context your code executes in determines what APIs you can use and how to debug it.
Location: packages/astro/src/core/
When it runs: During astro dev, astro build, and CLI commands
Characteristics:
Note: core/ is not purely build-time - avoid Node.js APIs unless in Vite plugin implementations
Examples:
packages/astro/src/core/build/ → Build orchestration (mixed)packages/astro/src/core/dev/ → Dev server setup (mixed)packages/astro/src/cli/ → CLI commands (pure Node.js)Debug with: Standard Node debugging, console.log, node --inspect
Location: packages/astro/src/runtime/server/
When it runs: Inside Vite's SSR environment during rendering
Characteristics:
node:fs, node:path, etc.)@astrojs/internal-helpers for cross-platform utilitiesRule: If the file path contains /runtime/, absolutely NO Node.js APIs
Examples:
packages/astro/src/runtime/server/render/ → Page renderingDebug with: DEBUG=astro:* environment variable
Location: packages/astro/src/runtime/client/
When it runs: In the browser after page load
Characteristics:
Examples:
packages/astro/src/runtime/client/idle.ts → Idle hydrationpackages/astro/src/runtime/client/visible.ts → Visible hydrationDebug with: Browser DevTools, console.log in browser
Astro uses a base Pipeline class with multiple implementations for different environments.
Pipeline (abstract base class)
├── RunnablePipeline → Dev with RunnableDevEnvironment
├── NonRunnablePipeline → Dev with NonRunnableDevEnvironment
├── BuildPipeline → Build and prerendering
├── AppPipeline → Production SSR/serverless
└── ContainerPipeline → Container API
Location: packages/astro/src/core/base-pipeline.ts
When: During astro dev with Vite RunnableDevEnvironment
Purpose: Handle dev server requests with runtime module loading
Location: packages/astro/src/vite-plugin-app/pipeline.ts
Characteristics:
Best practice: Avoid runtime module imports when possible. Prefer Vite plugins and virtual modules instead.
When: During astro dev with Vite NonRunnableDevEnvironment
Purpose: Handle dev server requests without runtime module loading
Location: packages/astro/src/core/app/dev/pipeline.ts
Characteristics:
Critical: This is why code must avoid runtime module imports and use plugins/virtual modules instead
When: During astro build and prerendering
Purpose: Generate static HTML and handle prerendering
Location: packages/astro/src/core/build/pipeline.ts
Characteristics:
When: Production runtime (serverless/SSR)
Purpose: Serve dynamic requests in production
Location: packages/astro/src/core/app/pipeline.ts
Characteristics:
When: Using the Container API
Purpose: Programmatic rendering outside of normal Astro contexts
Location: packages/astro/src/container/pipeline.ts
Characteristics:
Pipeline (abstract base class): Static parts that don't change between requests
From packages/astro/src/core/base-pipeline.ts:
All pipeline implementations extend this base class
Pipeline: Constant per server/build session (static)
RenderContext: Per-request data (dynamic)
Virtual modules are a core pattern in Astro. They're "files" that don't exist on disk but are generated at build/dev time.
Prefix: virtual:astro:* for Astro-internal, \0 prefix for Rollup-internal (Rollup convention)
Pattern:
virtual:astro:somethingresolveId hook returns \0virtual:astro:somethingload hook returns generated codeFile: packages/astro/src/virtual-modules/
| Module | Purpose | Source |
|---|---|---|
virtual:astro:routes | Route definitions | Generated from pages/ |
virtual:astro:manifest | Serialized manifest (single source of truth) | Build output |
virtual:astro:pages | Page collections | Generated from pages/ |
virtual:astro:renderers | Framework renderers | Integration config |
virtual:astro:middleware | Middleware module | src/middleware.ts |
virtual:astro:dev-css | Dev-mode CSS modules | Vite dev |
virtual:astro:app | App pipeline | Production runtime |
virtual:image-service | Image service config | Config + integrations |
Actions Virtual Modules:
virtual:astro:actions/entrypoint → Actions entryvirtual:astro:actions/options → Actions configurationAdapter Virtual Modules:
virtual:astro:adapter-config → Adapter configurationvirtual:astro:adapter-entrypoint → Adapter entry pointPrefix: @astro-page:* (legacy pattern, specific to pages)
Note: This is an older pattern used specifically for page modules. New virtual modules should use virtual:astro:* instead unless there's a specific reason to deviate.
Example: @astro-page:src/pages/index.astro → Virtual module for index page
Standard pattern for new plugins: Use virtual:astro:* prefix
Current pattern (see packages/astro/src/core/build/plugins/plugin-ssr.ts):
const VIRTUAL_MODULE_ID = 'virtual:astro:my-module';
const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID;
// In Vite plugin
{
name: '@astrojs/vite-plugin-my-module',
resolveId: {
filter: {
id: new RegExp(`^${VIRTUAL_MODULE_ID}$`),
},
handler() {
return RESOLVED_VIRTUAL_MODULE_ID;
},
},
load: {
filter: {
id: new RegExp(`^${RESOLVED_VIRTUAL_MODULE_ID}$`),
},
handler() {
return {
code: `export default ${JSON.stringify(data)}`,
};
},
},
}
Pattern: Use filter/id/handler structure for both resolveId and load hooks.
The content layer handles content collections at build time and runtime.
Location: packages/astro/src/content/
| Component | File | Purpose |
|---|---|---|
| Content Layer | content-layer.ts (479 lines) | Main orchestration |
| Data Store | data-store.ts | Read-only runtime store |
| Mutable Data Store | mutable-data-store.ts | Build-time mutable store |
| Runtime API | runtime.ts | getCollection(), getEntry() |
| Types Generator | types-generator.ts | TypeScript type generation |
| Watcher | watcher.ts | File system watching |
Build Time:
┌─────────────┐ ┌──────────────┐ ┌────────────────┐
│ Loaders │───▶│MutableStore │───▶│.astro/data- │
│(load data) │ │(collect data)│ │store.json │
└─────────────┘ └──────────────┘ └────────────────┘
│
▼
┌──────────────┐
│Types Generator│
└──────────────┘
Runtime:
┌────────────────┐ ┌──────────┐ ┌─────────────┐
│.astro/data- │───▶│DataStore │───▶│Runtime API │
│store.json │ │(read-only)│ │(getCollection)│
└────────────────┘ └──────────┘ └─────────────┘
File: packages/astro/src/content/consts.ts
DATA_STORE_FILE = '.astro/data-store.json';
COLLECTIONS_MANIFEST_FILE = 'collections-manifest.json';
MODULES_IMPORTS_FILE = 'modules-imports.json';
ASSET_IMPORTS_FILE = 'asset-imports.json';
New pattern: Fetch data at runtime rather than build time
Configuration: Separate from build-time collections
src/content/config.tssrc/live.config.tsLoader Protocol: Loaders must implement standard interface
Location: packages/astro/src/vite-plugin-*/
Vite plugins execute within Vite's context (similar to runtime/server).
Location: packages/astro/src/core/build/plugins/
Documentation: packages/astro/src/core/build/plugins/README.md
Plugin Responsibilities:
middleware.mjs if presentrenderers.mjsmanifest.mjs (single source of truth)Plugins work together in sequence:
packages/
├── astro/ # Core package
│ ├── src/
│ │ ├── core/ # Node.js build/dev
│ │ ├── runtime/ # Vite SSR + browser
│ │ ├── virtual-modules/ # Virtual module sources
│ │ ├── content/ # Content layer
│ │ ├── vite-plugin-*/ # Vite plugins
│ │ └── types/ # Centralized types
│ └── test/
│ ├── fixtures/ # Test fixtures
│ └── *.test.js # Unit tests
├── integrations/
│ ├── react/
│ ├── vue/
│ ├── svelte/
│ └── ...
└── create-astro/ # CLI scaffolding tool
examples/ # Example projects
├── blog/
├── minimal/
└── ...
When you see errors in node_modules/, map back to source:
ERROR in node_modules/astro/dist/core/build/index.js
↓
FIX in packages/astro/src/core/build/index.ts
ERROR in node_modules/@astrojs/react/index.js
↓
FIX in packages/integrations/react/src/index.ts
Rebuild required: Edits to source files take effect after pnpm run build
For detailed constraints including Node.js API restrictions, test isolation, virtual module conventions, and more, see constraints.md.
Key points:
runtime/ foldersoutDirsrc/types/virtual:astro:* prefixFor comprehensive debugging strategies, see debugging.md.
Quick reference by context:
DEBUG=astro:* or node --inspectDEBUG=astro:render,astro:server.astro/data-store.jsonpackages/astro/src/core/README.mdpackages/astro/src/core/build/plugins/README.mdpackages/astro/src/virtual-modules/README.md