Runtime
The synthesized Astro app that ships inside tangly.
~ 2 min read
Runtime
packages/tangly/runtime/ is a real Astro project that ships inside the tangly npm package. The CLI invokes astro dev / astro build programmatically with root pointing at this directory.
Layout
runtime/
- astro.config.mjs remark/rehype pipeline, MDX, Tailwind, adapter
src/
- content.config.ts collection — glob over
<userRoot>/**/*.mdx pages/
- index.astro redirects to first nav page
[...slug].astrocatch-all, renders any page slug
lib/
- mdx-components.ts injects Note/Card/Tabs/etc. into MDX
- remark-mintlify-compat.mjs
- remark-explicit-ids.mjs
- remark-mermaid.mjs
- content.config.ts collection — glob over
Page render pipeline
flowchart TD
R1[browser request<br/>/some-slug] --> R2["[...slug].astro<br/>getStaticPaths"]
R2 --> R3{slug type?}
R3 -- mdx page --> R4[load MDX entry<br/>via content collection]
R3 -- synth openapi --> R5[fetch spec<br/>extract operation]
R4 --> R6[lookup PageEntry<br/>virtual:tangly/manifest]
R5 --> R6
R6 --> R7[resolve mdxComponents<br/>+ shadowed overrides]
R7 --> R8[Layout]
R8 --> R9[TopNav<br/>Sidebar<br/>PageShell]
R9 --> R10[content.astro Content<br/>or OpenApiEndpoint]
R10 --> R11[Footer + SearchModal]
R11 --> R12[static HTML in dist/]
Catch-all in detail
src/pages/[...slug].astro is the heart of the runtime. For each request:
-
getStaticPaths
Reads
getCollection('docs')(Astro’s content layer) and returns one path per MDX entry, plus one per OpenAPI synth page frommanifest.pages. All routes prerendered. -
Resolve PageEntry
Looks up the entry in
virtual:tangly/manifestto get nav/sidebar/breadcrumb context. Falls back to a synthesized “orphan” entry for pages on disk but not in nav. -
OpenAPI handling
If frontmatter has
openapi: METHOD pathor the page is a synth endpoint, fetches the spec and renders via<OpenApiEndpoint>(or a third-party viewer ifapi.viewerisscalar/redoc/stoplight). -
Render
Wraps content in
Layout+PageShell, passesmdxComponentsinto<Content components={...} />. The Tangly Astro integration has already aliased every component import so user overrides at<userRoot>/theme/<Name>.astrointercept transparently.
Content collection
const docs = defineCollection({
loader: glob({
base: process.env.TANGLY_USER_ROOT,
pattern: ["**/*.mdx", "!**/_*.mdx", "!snippets/**", "!components/**", "!templates/**"],
}),
});Astro’s content layer handles MDX parsing, image processing, and per-file caching. We don’t reinvent any of it.
Why a runtime, not a config preset
We could ship as astro.config snippets the user adds. Instead, the runtime is fully synthesized:
- User repo never needs Astro deps.
tangly ejectmaterializes this directory into the user’s repo and removestanglyfrom deps when you want to leave.- We can iterate runtime internals without breaking user code.
Discovery
getRuntimeDir() finds the runtime by walking up from the CLI’s compiled JS until it finds a directory with astro.config.mjs. This handles npm install + workspace symlinks transparently.
Adapter selection
astro.config.mjs reads TANGLY_ADAPTER (set by the CLI from --adapter or auto-detect). Today only vercel applies a real adapter; cloudflare and node are recognized but stay static-only until SSR routes (e.g. AI chat) land. See Deploying for the matrix.