add-content
Skill106 starsChanged 7 months agoMITView on GitHub
Wire `@pracht/content` collections and `@pracht/markdown` routes: source discovery, Markdown/MDX pages, locales, and `llms.txt` or raw-source artifacts. Use for "add a blog", "set up docs", "render markdown pages", "add MDX", "content collections", "my markdown route 404s".
- Installs packages
1 other project uses a version of this.
Adopted by
Most recently taken up by
- Marid5/ai-tutortheir own version, first seen Oct 13, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- Marid5/ai-tutor★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository33 other files it gives its agents.
- AGENTS.mdAGENTS.md
- add-authSkill
- add-capabilitiesSkill
- add-dbSkill
- add-i18nSkill
- add-imagesSkill
- add-observabilitySkill
- add-openapiSkill
- audit-a11ySkill
- audit-agent-surfaceSkill
- audit-authSkill
- audit-bundlesSkill
- audit-csrfSkill
- audit-depsSkill
- audit-headersSkill
- audit-islandsSkill
- audit-loadersSkill
- audit-redirectsSkill
- audit-secretsSkill
- audit-seoSkill
- audit-shellsSkill
- configure-isgSkill
- migrate-nextjsSkill
- pracht-debugSkill
- pracht-deploySkill
- pracht-scaffoldSkill
- pracht-test-apiSkill
- pre-deploySkill
- scaffold-e2eSkill
- scaffold-testsSkill
- tune-render-modeSkill
- typed-routesSkill
- upgrade-prachtSkill
The file
We split this skill into the rules it gives an agent
9 rules. About 0 are common, 0 are distinctive, and 9 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
9
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 34
Ask with AskUserQuestion when it is not obvious from the repo:
- Very specific: line 57
Put the collection in its own module (content.ts) so the vite config, build plugins, and tests import the same object:
- Very specific: line 132
Use "error" for a docs site, "ignore" for a data-only collection.
- Very specific: line 156
Every accessor is async: each document's raw/body/compiled lives in its own deferred chunk, so the first content-backed request does not …
- Very specific: line 169
Add types once: "types": ["@pracht/markdown/client", "@pracht/content/virtual"] in tsconfig (or triple-slash references in any .d.ts).
- Very specific: line 219
Keep the literal defineCapability({ ...
- Very specific: line 242
Never import a capability-facing or loader-facing collection from client code — import virtual:pracht/content/<name> inside server code …
- Very specific: line 245
Do not enable the core llmsTxt option and a collection /llms.txt artifact at the same time.
- Very specific: line 249
Never overwrite an existing vite.config.ts, wrangler.jsonc, or content module — diff first and confirm collisions with AskUserQuestion.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Pracht Add Content Collections
@pracht/content is the optional, server-only content layer
(https://pracht.resynapse.dev/docs/content). It exists so a content-heavy app has one registry
instead of two readers that drift — route modules reading source through Vite,
and sitemap/llms.txt/search plugins scanning the filesystem again.
@pracht/markdown is the opinionated Markdown route-module compiler on top of
it (node_modules/@pracht/markdown/README.md).
Nothing here is on by default: adding a collection publishes no source and creates no agent surface until you add artifacts or capabilities.
Step 1: Pick the shape
Ask with AskUserQuestion when it is not obvious from the repo:
| Situation | Wiring |
| Markdown/MDX files that should be pages | @pracht/markdown → defineMarkdownCollection() |
| Content that feeds loaders, search, or an API — never a page | @pracht/content → defineCollection() with unroutedDocuments: "ignore" |
| An app-specific compiler (custom AST, page model, search record) | defineCollection({ compile, module }) |
Also settle: collection root directory, routeBase, whether documents are
localized, and whether relative images appear in the Markdown.
Step 2: Install
pnpm add @pracht/content
pnpm add @pracht/markdown @pracht/image # Markdown route modules
pnpm add -D sharp # only when documents embed local imagessharp runs at build/dev time only and never ships to a runtime bundle.
Step 3: Define the collection outside the vite config
Put the collection in its own module (content.ts) so the vite config, build
plugins, and tests import the same object:
// content.ts
import { llmsTxtArtifacts, rawContentArtifacts } from "@pracht/content";
import { defineMarkdownCollection } from "@pracht/markdown";
export const docs = defineMarkdownCollection({
name: "docs",
root: new URL("./src/routes/docs", import.meta.url),
routeBase: "/docs",
// locales: { default: "en", supported: ["en", "fr"] },
// images: { placeholder: "blur" }, // default "empty" — keeps CSP unchanged
// snapshot: { raw: false }, // drop a representation you never read
artifacts: [
rawContentArtifacts({ path: (document) => `${document.path}.md` }),
llmsTxtArtifacts({
title: "Product docs",
origin: "https://example.com",
sections: [{ heading: "Docs", match: "/docs" }],
}),
],
});Without sources, the registry scans root recursively for .md/.mdx;
index files collapse to their directory. Pass explicit
{ id, path, source, locale } entries when routes must not be inferred.
Duplicate ids, routes, sources, or artifact paths throw at definition time
rather than letting file order pick a winner.
Step 4: Register the plugins (order matters)
// vite.config.ts
import { prachtContent } from "@pracht/content/vite";
import { prachtImage } from "@pracht/image/vite";
import { pracht } from "@pracht/vite-plugin";
import { defineConfig } from "vite";
import { docs } from "./content";
export default defineConfig({
plugins: [
prachtContent({ collections: [docs] }), // one call, every collection
prachtImage(), // relative Markdown images
pracht(),
],
});Register all collections through a single prachtContent() call — the
plugin owns one internal build manifest and rejects a second registration.
Step 5: Point routes at the documents
Markdown modules are ordinary route modules (they export Component, head(),
and the raw markdown string for Accept: text/markdown negotiation):
route("/docs/routing", () => import("./routes/docs/routing.md"), {
id: "routing",
render: "ssg",
});Sources and routes are still two readers, so pracht build reconciles them and
names every document (and generated locale alias) that no route serves — a
document without a route still reaches llms.txt and rawContentArtifacts()
while the page answers 404. The default policy is "warn":
prachtContent({ collections: [docs], unroutedDocuments: "error" });Use "error" for a docs site, "ignore" for a data-only collection. On a
static export a dynamic SSG route only covers the concrete paths
getStaticPaths() returns, and a dynamic SPA route only covers deep links when
staticAdapter({ fallback }) emits one. pracht verify cannot do this check —
it reads the vite config as text — so run a build before believing the routing
is complete.
Step 6: Read the collection at request time
Loaders, middleware, API routes, and capabilities must import the generated snapshot, not the filesystem-backed authoring object:
import { contentLoader } from "@pracht/content/runtime";
import docs from "virtual:pracht/content/docs";
export const loader = contentLoader(docs, {
select: (document) => ({ html: document.compiled, title: document.frontmatter.title }),
});- The virtual module is server-only; a retained client import fails the build instead of shipping source, frontmatter, and compiled values to the browser.
- Every accessor is async: each document's
raw/body/compiledlives in its own deferred chunk, so the first content-backed request does not parse the whole collection.iterate()streams one document at a time;all()loads everything. contentLoader()uses the matched, base-freepathname, and answers unmatchable pathnames (/docs/%2e%2e) with its not-found path instead of failing the request.markdownRepresentation(document, "raw" | "body")produces the server-onlymarkdownexport for content negotiation — it throws whensnapshotdropped the field you selected.- Frontmatter and compiled values must be JSON-serializable; the build names the offending value path otherwise.
Add types once: "types": ["@pracht/markdown/client", "@pracht/content/virtual"]
in tsconfig (or triple-slash references in any .d.ts).
Step 7: Artifacts and the two llms.txt files
llmsTxtArtifacts() (collection-driven, curated, can emit an llms-full.txt)
is not the same as the core llmsTxt plugin option (app-graph driven,
https://pracht.resynapse.dev/docs/agents#llmstxt). Both default to /llms.txt, and enabling both fails the
build rather than silently overwriting — pick one, or give the collection a
distinct summaryPath. A third, unrelated file is pracht llms, which prints
the authoring guide for coding agents; pracht llms --write drops it in the
app root, which is exactly the confusing filename collision to avoid in an app
that publishes its own index.
For agents, link entries to Markdown and split the full text by section:
link: (document) => `${document.path}.md` (only when those URLs exist,
e.g. via rawContentArtifacts()), and fullPath: "/llms-guides.txt" on a
section to emit a file with just its pages (with origin set, each page gets
a Source: line with its page URL; sort orders a section's entries). Section files and
/llms-full.txt are listed under a trailing ## Optional heading; a section
that matches nothing emits no file. Duplicate or file/directory-colliding
artifact paths throw when the helper is defined.
Localization trap: a string section.match is compared against the
locale-neutral route, so match: "/docs" indexes only the default locale
while rawContentArtifacts() publishes every translation. Pass a match
function to index one locale deliberately.
Artifact paths are preflighted: canonical ASCII segments only, no overlap with
publicDir, bundle output, prerendered pages, request-time page/API paths,
concrete ISG paths, the /_pracht namespace, or Netlify's /_headers and
/_redirects.
Step 8: Deployment notes
- Cloudflare — deferred content chunks require
"no_bundle": trueand anESModulerule covering"**/*.js". Thedist/server/wrangler.jsonthatpracht buildgenerates has both; awrangler.jsoncthat sets its ownmainneeds them too, andpracht verifywarns when either is missing. - Content type headers — explicit artifact
contentTypevalues flow into the production headers manifest and are applied by the Node, Cloudflare, Netlify, and Vercel adapters. - Bundle cost — payload chunks carry roughly two to three times the source
bytes.
snapshot: { raw: false }or{ body: false }drops a representation the app never reads;compiledand frontmatter cannot be dropped.
Step 9: Optional agent surface
@pracht/content/capabilities exports createContentPageCapability() and
createContentSearchCapability(), which return input/output/run fields.
Keep the literal defineCapability({ ... }) in the app so pracht verify can
audit exposure and policy statically — see /add-capabilities. Both helpers
read document.body and refuse a body-free snapshot when constructed.
Step 10: Verify
pracht build # reconciliation warnings + artifact preflight
pracht verify --json
pracht typegen # after route ids or paths changeThen load a content route in pracht dev, edit a source file, and confirm the
watcher invalidates it (collection roots outside Vite's project root are added
to the watcher explicitly).
Rules
- Compiled Markdown is executed as HTML, unsanitized. The generated route
module renders through
dangerouslySetInnerHTML. Only compile repo-authored, reviewed content; sanitize inparseorrender(e.g.sanitize-html) before compiling anything from a CMS, a database, or a user. - Never import a capability-facing or loader-facing collection from client
code — import
virtual:pracht/content/<name>inside server code only. - One
prachtContent()call, with every collection in it. - Do not enable the core
llmsTxtoption and a collection/llms.txtartifact at the same time. - Adding a collection must not publish raw source implicitly — add
rawContentArtifacts()only when publishing sources is intended. - Never overwrite an existing
vite.config.ts,wrangler.jsonc, or content module — diff first and confirm collisions withAskUserQuestion.
$ARGUMENTS
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

