stnd.buildSTANDARD MANUAL2026-09-20

@stnd/loader

@stnd/loader

Overview

Content ingestion loaders for the Standard Ecosystem, built as custom Astro Content Collection Loaders.

ELI5

This is how an Astro site’s content collections point at something that isn’t a local src/content/ folder — an Obsidian vault (with wikilinks and frontmatter) or a Notion database. Define the collection once with the right loader, then use getCollection() like any normal Astro content collection.

Usage

Use it (vault):

import { defineCollection } from "astro:content";
import { vaultLoader } from "@stnd/loader-vault";
export const collections = {
  vault: defineCollection({ loader: vaultLoader({ path: "../../vault/Atelier" }) }),
};

Loaders

1. Vault Loader (@stnd/loader-vault)

Loads content from a local directory containing Markdown files with Obsidian-style frontmatter, wikilinks ([[link]]), and media files.

Configuration (src/content.config.ts)

import { defineCollection } from "astro:content";
import { vaultLoader } from "@stnd/loader-vault";

const vault = defineCollection({
  loader: vaultLoader({
    path: "../../vault/Atelier", // path to the local Obsidian vault
    flattenDirectories: false,   // preserve directory structure in IDs
    ignore: [".obsidian", ".trash", ".agent"], // directories to ignore
  }),
});

export const collections = { vault };

Astro Dev Mode Workaround (Important)

Warning

In Astro 6.x/7.x development mode (astro dev), calling getCollection("vault") inside routes injected via injectRoute() (used by plugins/integrations) returns []. This is due to virtual module hydration issues inside Vite SSR context.

To work around this, the vault loader populates a global singleton that survives HMR and SSR code-splitting. Instead of using getCollection, import the vault singleton directly:

import { vault } from "@stnd/loader-vault";

// Wait for the vault to initialize (optional, useful in edge cases)
await vault.ensureLoaded();

// Access entries and metadata
const entries = vault.entries; // Array of VaultEntry
const size = vault.size;       // Number of entries
const meta = vault.meta;       // { validLinks, imageIndex, vaultPath }

// Find individual entries
const note = vault.getEntry("my-permalink");

2. Notion Loader (@stnd/loader-notion)

Synchronizes content from Notion databases into the Standard Ecosystem.

Configuration (src/content.config.ts)

import { defineCollection } from "astro:content";
import { notionLoader } from "@stnd/loader-notion";

const notion = defineCollection({
  loader: notionLoader({
    auth: import.meta.env.NOTION_API_KEY,
    databaseId: import.meta.env.NOTION_DATABASE_ID,
  }),
});

export const collections = { notion };

Notes / Observations

  • Reciprocal symlinks are deduped by real path. A vault folder that symlinks back into a directory containing the vault itself (e.g. this repo’s own vault/Atelier -> ~/Documents/Atelier alongside ~/Documents/Atelier/Utopie -> this repo — an intentional “codebase opens as a vault too” pattern) turns glob({ follow: true }) into a cycle: the same real file gets enumerated repeatedly at increasing nesting depth. The loader now resolves each globbed path with fs.realpath and skips repeats, so every physical note loads exactly once regardless of how many symlinked routes reach it.
  • publish: accepts true or a date (matching the repo-wide publish convention used elsewhere, e.g. the D1-backed Garden notes): publish: true or any parseable publish: <date> loads the note; false/absent/unparseable skips it. A future date still loads (no scheduled-publish gate at this layer) — a consumer wanting to hide not-yet-due content filters vault.entries itself, as apps/stnd.gd/modules/rss/ does for its RSS feed.

Todo

  • Evaluate experimental.collectionStorage: 'chunked' for the vault collection — Astro 7.1.6 added a chunk-size option, and @astrojs/cloudflare 14.1.7 fixed chunked-collection loading on the adapter. Only worth it if the vault store is actually large enough to justify it; measure the serialized store first rather than adopting on principle. Note this is unrelated to the dev-mode getCollection workaround (root README #1), which chunking does not address. priority: 2 token_scale: 3 created: 2026-08-07 area: framework