architecture.md
3,188 tokens · o200k_base · 14,279 bytes
Architecture Guide
Understanding Astro's internal architecture is critical for effective development. This guide covers execution contexts, pipelines, virtual modules, and content layer architecture.
Three Execution Contexts
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.
Context 1: Node.js (Core)
Location: packages/astro/src/core/
When it runs: During astro dev, astro build, and CLI commands
Characteristics:
- Node.js environment (but contains mixed code)
- Contains both build-time AND runtime code
- Node.js API usage should be avoided except in Vite plugins
- Powers build orchestration and dev server setup
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
Context 2: Vite SSR (Runtime Server)
Location: packages/astro/src/runtime/server/
When it runs: Inside Vite's SSR environment during rendering
Characteristics:
- Node environment BUT isolated from core
- CANNOT use Node.js APIs (
node:fs,node:path, etc.) - Must work in non-Node runtimes (Cloudflare Workers, Deno)
- Use
@astrojs/internal-helpersfor cross-platform utilities
Rule: If the file path contains /runtime/, absolutely NO Node.js APIs
Examples:
packages/astro/src/runtime/server/render/→ Page rendering- Virtual modules loaded during SSR
Debug with: DEBUG=astro:* environment variable
Context 3: Browser (Runtime Client)
Location: packages/astro/src/runtime/client/
When it runs: In the browser after page load
Characteristics:
- Browser-only environment
- CANNOT use any Node.js APIs
- Only browser-compatible code allowed
- Handles partial hydration, client-side routing
Examples:
packages/astro/src/runtime/client/idle.ts→ Idle hydrationpackages/astro/src/runtime/client/visible.ts→ Visible hydration
Debug with: Browser DevTools, console.log in browser
Pipeline Architecture
Astro uses a base Pipeline class with multiple implementations for different environments.
Pipeline Hierarchy
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
Pipeline Types
1. RunnablePipeline
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:
- Contains reference to Vite loader system
- Can import modules at runtime via Vite environment APIs
- Hot module reloading support
- Standard dev workflow
Best practice: Avoid runtime module imports when possible. Prefer Vite plugins and virtual modules instead.
2. NonRunnablePipeline
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:
- Used by adapters like Cloudflare
- No reference to Vite loader system
- Cannot import modules at runtime
- Must rely entirely on Vite plugins and virtual modules
Critical: This is why code must avoid runtime module imports and use plugins/virtual modules instead
3. BuildPipeline
When: During astro build and prerendering
Purpose: Generate static HTML and handle prerendering
Location: packages/astro/src/core/build/pipeline.ts
Characteristics:
- Optimized output
- Static generation
- Asset processing
- Prerendering logic
4. AppPipeline
When: Production runtime (serverless/SSR)
Purpose: Serve dynamic requests in production
Location: packages/astro/src/core/app/pipeline.ts
Characteristics:
- No build-time dependencies
- Optimized for cold starts
- Adapter integration
- Production error handling
5. ContainerPipeline
When: Using the Container API
Purpose: Programmatic rendering outside of normal Astro contexts
Location: packages/astro/src/container/pipeline.ts
Characteristics:
- Standalone rendering
- Component interning cache
- Used for testing and programmatic access
Base Pipeline
Pipeline (abstract base class): Static parts that don't change between requests
From packages/astro/src/core/base-pipeline.ts:
- Configuration
- Logger
- Manifest
- Runtime mode (development/production)
- Middleware
- Actions
- Internal caches
All pipeline implementations extend this base class
RenderContext vs Pipeline
Pipeline: Constant per server/build session (static)
- Created once at process start
- Reused for every request
- Contains configuration and settings
RenderContext: Per-request data (dynamic)
- Created for each request
- Current URL
- Matched route
- Request locals
- i18n context
- Middleware execution
Virtual Modules System
Virtual modules are a core pattern in Astro. They're "files" that don't exist on disk but are generated at build/dev time.
Virtual Module Conventions
Prefix: virtual:astro:* for Astro-internal, \0 prefix for Rollup-internal (Rollup convention)
Pattern:
- Internal code imports
virtual:astro:something - Vite plugin
resolveIdhook returns\0virtual:astro:something - Vite plugin
loadhook returns generated code
Core Virtual Modules Registry
File: 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 configuration
Adapter Virtual Modules:
virtual:astro:adapter-config→ Adapter configurationvirtual:astro:adapter-entrypoint→ Adapter entry point
Internal Page Virtual Modules
Prefix: @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
Virtual Module Implementation Pattern
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.
Content Layer Architecture
The content layer handles content collections at build time and runtime.
Key Components
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 |
Content Layer Flow
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)│
└────────────────┘ └──────────┘ └─────────────┘
Content Constants
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';
Live Collections (Astro 5+)
New pattern: Fetch data at runtime rather than build time
Configuration: Separate from build-time collections
- Build-time:
src/content/config.ts - Runtime:
src/live.config.ts
Loader Protocol: Loaders must implement standard interface
Vite Plugin Architecture
Location: packages/astro/src/vite-plugin-*/
Vite plugins execute within Vite's context (similar to runtime/server).
Build Plugins
Location: packages/astro/src/core/build/plugins/
Documentation: packages/astro/src/core/build/plugins/README.md
Plugin Responsibilities:
- plugin-middleware: Emits
middleware.mjsif present - plugin-renderers: Collects renderers →
renderers.mjs - plugin-pages: Virtual modules for each page (static builds)
- plugin-ssr: Creates SSR entry points
- plugin-manifest: Creates
manifest.mjs(single source of truth)
Plugin Execution Order
Plugins work together in sequence:
- Middleware plugin runs first
- Renderers collected
- Pages processed
- SSR entries created
- Manifest generated (final step)
Package Structure
Monorepo Organization
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/
└── ...
Node Modules Mapping
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
Critical Constraints
For detailed constraints including Node.js API restrictions, test isolation, virtual module conventions, and more, see constraints.md.
Key points:
- Node.js APIs forbidden in
runtime/folders - Tests must use unique
outDir - Types centralized in
src/types/ - Virtual modules use
virtual:astro:*prefix
Debugging
For comprehensive debugging strategies, see debugging.md.
Quick reference by context:
- Core (Node.js): Use
DEBUG=astro:*ornode --inspect - Runtime Server: Check
DEBUG=astro:render,astro:server - Runtime Client: Use browser DevTools
- Content Layer: Inspect
.astro/data-store.json
Further Reading
- Core README:
packages/astro/src/core/README.md - Build Plugins:
packages/astro/src/core/build/plugins/README.md - Virtual Modules:
packages/astro/src/virtual-modules/README.md - CONTRIBUTING.md sections on code structure
Referenced from SKILL.md
Source excerpt starting at line 15.| -------------------------- | -------------------------------------------------------------------- | ---------------------------------- || Adding a core feature | [architecture.md](architecture.md), [constraints.md](constraints.md) | [testing.md](testing.md) || Fixing a bug | [debugging.md](debugging.md) | [architecture.md](architecture.md) || Writing/fixing tests | [testing.md](testing.md) | [constraints.md](constraints.md) |
Source excerpt starting at line 19.| Creating an integration | Explore `packages/integrations/` for examples | [testing.md](testing.md) || Understanding architecture | [architecture.md](architecture.md) | - || Dealing with errors | [debugging.md](debugging.md), [constraints.md](constraints.md) | [testing.md](testing.md) || Understanding constraints | [constraints.md](constraints.md) | [architecture.md](architecture.md) |
Source excerpt starting at line 30.3. **Test Isolation**: Must set unique `outDir` for each integration test → [testing.md](testing.md)4. **Runtime Boundaries**: Core vs Vite vs Browser execution contexts → [architecture.md](architecture.md)5. **Prerelease Mode**: Changesets target `origin/next` branch (check `.changeset/config.json`)
Source excerpt starting at line 74.**Note**: Error stack traces in `node_modules/` map to source in `packages/`. See [architecture.md](architecture.md) for details.
Source excerpt starting at line 101.See [architecture.md](architecture.md) for complete details.