Derived Modular Arch
Guides

Astro

DMA with composition root in pages/ and frontmatter imports.

Example for Astro 5: layers under src/, composition root in src/pages/, layout and providers in src/app/.

Sources: examples/astro-pages.

Two roots: pages and app

FolderRole
src/pages/Routes — thin, mount only
src/app/Layout, providers, seed — not routes

In pages/ — only routable files (index.astro). layout.astro and shop-providers.astro live in app/ so Astro does not create extra routes.

Thin route

---
import CatalogPage from "@/features/catalog/public/catalog-page.astro";
import ShopProviders from "@/app/shop-providers.astro";
import Layout from "@/app/layout.astro";
---

<Layout title="DMA · Astro mini-shop">
  <ShopProviders>
    <CatalogPage />
  </ShopProviders>
</Layout>

Imports only downward: features/*/public/*, services/*/public/*, shared/*.

Frontmatter = module script

The --- block runs at build time:

  1. Import */public/* and stage-0 modules
  2. Call service helpers before the template
  3. Pass data to children via props

dma check and the Biome plugin parse imports from .astro frontmatter the same as from .ts.

Provider pattern

shop-providers.astro binds a port before features render:

---
import { provideCartSeed } from "@/services/cart/public/ports";
import { demoCartSeed } from "@/app/cart-seed";

provideCartSeed(demoCartSeed);
---
<slot />

Ports are invisible to the static graph — use for seed/cycle break, not to bypass promotion. More — Cross-module wiring.

State patterns

  1. Colocated storecatalog.store.ts, relative import from public/
  2. Promoted serviceservices/cart/public/cart.ts (catalog + checkout)
  3. Provider portports.ts + bind in app/

Where to put code

TaskFolder
Route shellsrc/pages/*.astro
Layout, providerssrc/app/
Feature screenfeatures/<name>/public/<entry>.astro
Stage-0 modulefeatures/<name>.astro
2+ consumersservices/<name>/public/
Portable UI/libshared/ui/, shared/lib/

Styles

KindWhere
Scoped <style>In the same .astro file
Feature CSScolocated *.css → import from public/
CSS Modulespublic/*.module.css
Globalstyles/global.css → import from app/layout.astro

Global CSS — only from the composition root.

Tooling

bun run build       # astro build
bun run dma-check   # dma check .
bun run lint        # biome + @derived-modular/biome-plugin
bun run test

Biome plugin is best-effort (GritQL). No full path resolution or graph cycles. Always run dma check in CI.

What's next

On this page