astro-developer

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗

Comprehensive guide for developing in the Astro monorepo. Covers architecture, debugging, testing, and critical constraints. Use when working on features, fixes, tests, or understanding the codebase structure.

.agents/skills/astro-developer/SKILL.md

Download bundle ↓
main · 63ca3426 bundle filesScanned 2026-09-14

architecture.md

3,188 tokens · o200k_base · 14,279 bytes

Source excerpt starting at line 1.
# 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-helpers` for 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 hydration- `packages/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**: 1. Internal code imports `virtual:astro:something`2. Vite plugin `resolveId` hook returns `\0virtual:astro:something`3. Vite plugin `load` hook 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 entry- `virtual:astro:actions/options` → Actions configuration **Adapter Virtual Modules**: - `virtual:astro:adapter-config` → Adapter configuration- `virtual: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`): ```typescriptconst 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` ```typescriptDATA_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**: 1. **plugin-middleware**: Emits `middleware.mjs` if present2. **plugin-renderers**: Collects renderers → `renderers.mjs`3. **plugin-pages**: Virtual modules for each page (static builds)4. **plugin-ssr**: Creates SSR entry points5. **plugin-manifest**: Creates `manifest.mjs` (single source of truth) ### Plugin Execution Order Plugins work together in sequence: 1. Middleware plugin runs first2. Renderers collected3. Pages processed4. SSR entries created5. 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.jsFIX in packages/astro/src/core/build/index.ts ERROR in node_modules/@astrojs/react/index.jsFIX 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](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](debugging.md). **Quick reference by context:** - **Core (Node.js)**: Use `DEBUG=astro:*` or `node --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.