stnd.build

Module System

Vertical slices. One manifest. Delete the folder, the feature disappears.
The app keeps running. That's the litmus test.

How it works

Discover

Standard scans modules/ for *.module.js files and loads Gold Standard packages from @stnd/*.

Sort

Dependencies are resolved into a topological order. Circular dependencies throw immediately.

Wire

Routes, styles, scripts, middleware, hooks, and UI zones are injected into the Astro build pipeline.

Anatomy of a module

Every module is a folder with an index.module.js manifest. The manifest declares what the module contributes — routes, styles, hooks, UI — and the framework wires it all together.

modules/
└── my-feature/
    ├── index.module.js      ← The manifest (start here)
    ├── routes/
    │   └── index.astro      ← Pages
    ├── models/
    │   └── Thing.ts         ← Domain logic
    ├── ui/
    │   └── Widget.svelte    ← Components
    ├── actions.ts           ← Server actions
    ├── middleware.ts         ← Request pipeline
    └── styles/
        └── feature.scss     ← Scoped styles

Minimal example

The manifest

// modules/blog/index.module.js
export default {
  id: "blog",
  name: "Blog",
  description: "A simple blog.",

  routes: [
    { path: "/blog", entrypoint: "./routes/index.astro" },
    { path: "/blog/[slug]", entrypoint: "./routes/[slug].astro" },
  ],

  styles: ["./styles/blog.scss"],
};

That's it

# What you get:

* /blog route registered
* /blog/[slug] route registered
* blog.scss injected globally
* @modules/blog alias available
* Hot reload when manifest changes

# Delete the folder:

* Routes disappear
* Styles disappear
* App keeps running

The hooks API

The hooks key is the single unified API for all module contributions beyond routes and styles. It handles UI zones, lifecycle events, and action handlers — all in one place.

export default {
  id: "my-feature",
  name: "My Feature",

  hooks: {
    // ── UI zones ──────────────────────────────────────
    // Inject components into the page layout.
    // Use "ui" for the component path.
    // Plain strings are shorthand for { ui: "path" }.

    "stnd:base": ["./MyNotification.astro"],
    //            ↑ shorthand — no meta needed

    "stnd:client": [
      {
        ui: "./MyWidget.svelte",
        meta: { "client:load": true },
      },
    ],

    // ── Lifecycle hooks ───────────────────────────────
    // Run code at build stages. Points to JS/TS files.

    "astro:build:done": [{ action: "./on-build-done.js" }],

    // ── Custom hooks ──────────────────────────────────
    // Launcher views, actions, or any custom system.

    "launcher:view": [
      {
        ui: "./views/MyView.svelte",
        trigger: "::myview",
        meta: { title: "My View", icon: "ph:star" },
      },
    ],

    "launcher:action": [
      { action: "./actions/my-action.js", meta: { label: "Do Thing" } },
    ],
  },
};

Resolution rules

Hook name Entry type Result
astro:* Any Astro lifecycle hook
Anything else .js / .ts Runtime action handler
Anything else .astro / .svelte UI zone contribution
Anything else Plain string UI zone contribution (shorthand)

Hook zones

Zones are named injection points in the layout where modules contribute UI. The framework Base layout renders two built-in zones. Apps can define more.

stnd:base

Static Astro components rendered at build time. No JavaScript overhead. Perfect for HTML + inline scripts.

  • Toast notification container
  • Confetti celebration script
// Rendered in Base.astro as:
<Hook zone="stnd:base" />

stnd:client

Hydrated Svelte components with client:load. For anything that needs client-side interactivity.

  • Lab (CSS token inspector)
  • DropZone (drag & drop uploads)
  • Graft Toolbar (text selection actions)
// Rendered in Base.astro as:
<Hook zone="stnd:client" hydrated={true} />

Custom zones

Apps can define their own zones. Just render a <Hook> in your layout and contribute to it from any module.

In your layout

---
import Hook from "@stnd/core/Hook";
---

<aside>
  <Hook zone="sidebar" />
</aside>

In any module

// modules/weather/index.module.js
export default {
  id: "weather",
  name: "Weather Widget",
  hooks: {
    "sidebar": ["./WeatherCard.astro"],
  },
};

The system layer

Every page rendered with Base.astro includes a system layer that persists across navigations. Gold Standard modules inject their UI here automatically — you never import Toast, Confetti, or Lab manually.

<!-- Inside @stnd/layout/Base.astro -->
<div id="standard-os" transition:persist="standard-os">

  <!-- Zero-JS: Toast container, Confetti script -->
  <Hook zone="stnd:base" />

  <!-- Hydrated: Lab inspector, app extensions -->
  <Hook zone="stnd:client" hydrated={true} />

</div>

Gold Standard modules

Every @stnd/core site ships with these modules by default. No configuration needed — they're loaded automatically. Opt out of any module with moduleExclude.

System UI

Toast Notifications

Global notification system. Provides window.toast() on every page.

@stnd/modules/toast stnd:base

Confetti

Page-load celebration effect via canvas-confetti CDN.

@stnd/modules/confetti stnd:base

Lab

Live CSS token inspector and design debug tools.

@stnd/modules/lab stnd:client

Launcher

Universal command palette engine — views, actions, and keyboard navigation.

@stnd/modules/launcher stnd:client

Styles & Fonts

Styles

Golden ratio typography, Swiss grid, OKLCH color system.

@stnd/modules/styles

Inter

Self-hosted Inter typeface.

@stnd/fonts/inter

Source Serif 4

Self-hosted Source Serif 4 typeface.

@stnd/fonts/source-serif-4

IBM Plex Mono

Self-hosted IBM Plex Mono typeface.

@stnd/fonts/ibm-plex-mono

Client Enhancements

SEO & Web Standards

Copy Buttons

Automatic copy-to-clipboard buttons on code blocks.

@stnd/modules/copy-buttons

Image Zoom

Click-to-zoom on images for detail inspection.

@stnd/modules/image-zoom

Scroll Wrappers

Horizontal scroll containers for wide tables and code.

@stnd/modules/scroll-wrappers

Robots

Auto-generated robots.txt for search engine crawling.

@stnd/modules/robots

Headers

Security and caching HTTP headers.

@stnd/modules/headers

Manifest

Web app manifest for PWA installability.

@stnd/modules/manifest

Sitemap

Auto-generated sitemap.xml for search engines.

@stnd/modules/sitemap

Opt out

Don't want confetti? Exclude it in your Astro config. The module is skipped entirely — no code, no styles, no scripts.

// astro.config.mjs
import standard from "@stnd/core";

export default defineConfig({
  integrations: [
    standard({
      moduleExclude: [
        "@stnd/modules/confetti",     // No celebration
        "@stnd/modules/lab",          // No debug inspector
      ],
    }),
  ],
});

Add your own modules

Load additional @stnd/* modules or any npm package that exports a module manifest.

// astro.config.mjs
import standard from "@stnd/core";

export default defineConfig({
  integrations: [
    standard({
      moduleLoad: [
        "@stnd/modules/rss",          // Add RSS feed
        "@stnd/modules/humans",       // Add humans.txt
        "@stnd/fonts/cargo-diatype",  // Add a typeface
      ],
    }),
  ],
});

App-level modules are discovered automatically from your modules/ folder — no registration needed.

Manifest reference

Every key a module manifest can declare. All keys are optional except id and name.

Key Type Description
id * string Unique module identifier.
name * string Human-readable display name.
description string What this module does.
status "enabled" | "disabled" Set to "disabled" to skip loading.
dependencies string[] Module IDs this module depends on.
routes array Astro routes to inject.
styles string[] CSS/SCSS files to inject globally.
scripts string[] Client scripts to inject (paths or CDN URLs).
middleware array Server middleware to register.
actions string Path to Astro actions file.
hooks object UI zones and lifecycle hooks.
aliases object Vite path aliases (e.g. @myfeature → ./src).
content string Content collection configuration.
config object Contribute to the global site config (nav, title, etc.).

Hook entry formats

Each hook zone accepts an array of entries. Entries can be strings (shorthand) or objects with ui, action, meta, and trigger.

hooks: {
  "stnd:base": [
    // String shorthand — just a path, no metadata
    "./MyComponent.astro",
  ],

  "stnd:client": [
    // Object — full control
    {
      ui: "./MyWidget.svelte",       // The component to render
      meta: { "client:load": true }, // Props / hydration directives
    },
  ],

  "launcher:view": [
    // Launcher view — ui + trigger + meta
    {
      ui: "./views/SearchView.svelte",
      trigger: "::search",
      meta: {
        title: "Search",
        icon: "ph:magnifying-glass",
        size: { width: "standard", height: "standard" },
      },
    },
  ],

  "launcher:action": [
    // Action handler — points to a JS file
    {
      action: "./actions/navigation.js",
      meta: { label: "Navigation" },
    },
  ],
}

Full example

A complete module manifest using every available key.

// modules/upload-system/index.module.js
export default {
  id: "upload-system",
  name: "Upload System",
  description: "Global drag and drop zone for file uploads.",

  dependencies: ["root"],

  routes: [
    { path: "/uploads", entrypoint: "./routes/index.astro" },
  ],

  styles: ["./styles/dropzone.scss"],

  scripts: ["./client/upload-progress.js"],

  middleware: [
    { entrypoint: "./middleware.ts", order: 10 },
  ],

  actions: "./actions.ts",

  hooks: {
    "stnd:client": [
      {
        ui: "./GlobalDropZone.svelte",
        meta: { "client:load": true },
      },
    ],
  },

  config: {
    maxUploadSize: "10MB",
  },

  aliases: {
    "@uploads": ".",
  },
};

Module lifecycle

Modules are loaded once during astro:config:setup and their contributions are wired into the build pipeline.

Discover Import Sort deps Wire Build
  1. Discover — Scans modules/ for *.module.js files and resolves moduleLoad packages.
  2. Import — Dynamic-imports each manifest. Validates that every module has a unique id.
  3. Sort deps — Builds a dependency graph and produces a topological load order. Circular dependencies throw.
  4. Wire — Routes are injected via injectRoute. Styles and scripts become virtual modules. Middleware is registered. Hook zones are populated. Aliases are set.
  5. Build — Astro takes over. Virtual modules are resolved. Components render. Everything is bundled.

Import rules

Modules follow strict import boundaries. This keeps the architecture clean and ensures any module can be deleted without breaking others.

From Can import Cannot import
Any module @stnd/* packages Other app modules
Any module @modules/core/* (the foundation)
Feature module Its own files Another feature module
Core module Its own files Feature modules
// ✓ Good — importing from @stnd packages
import Icon from "@stnd/icon/Icon.svelte";
import { slugify } from "@stnd/utils";

// ✓ Good — importing from the core module
import Note from "@modules/core/models/Note";

// ✗ Bad — cross-importing between feature modules
import Thing from "@modules/upload-system/Thing";  // NO

Quick reference

I want to… Manifest key
Add a page routes: [{ path, entrypoint }]
Add global CSS styles: ["./my.scss"]
Add a client script scripts: ["./my.js"]
Add server middleware middleware: [{ entrypoint, order }]
Add server actions actions: "./actions.ts"
Inject UI into the base layout hooks: { "stnd:base": ["./My.astro"] }
Inject a hydrated Svelte component hooks: { "stnd:client": [{ ui, meta }] }
Add a Launcher view hooks: { "launcher:view": [{ ui, trigger, meta }] }
Add a Launcher action hooks: { "launcher:action": [{ action, meta }] }
Set a Vite alias aliases: { "@mine": "." }
Contribute to site config config: { nav: [...] }
Depend on another module dependencies: ["other-module-id"]
Disable a module status: "disabled"