Tangly v0.2 ships richer code blocks, page chrome, and more — see what's new

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

  • Directoryruntime/
    • astro.config.mjs remark/rehype pipeline, MDX, Tailwind, adapter
    • Directorysrc/
      • content.config.ts collection — glob over <userRoot>/**/*.mdx
      • Directorypages/
        • index.astro redirects to first nav page
        • [...slug].astro catch-all, renders any page slug
      • Directorylib/
        • mdx-components.ts injects Note/Card/Tabs/etc. into MDX
        • remark-mintlify-compat.mjs
        • remark-explicit-ids.mjs
        • remark-mermaid.mjs

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:

  1. getStaticPaths

    Reads getCollection('docs') (Astro’s content layer) and returns one path per MDX entry, plus one per OpenAPI synth page from manifest.pages. All routes prerendered.

  2. Resolve PageEntry

    Looks up the entry in virtual:tangly/manifest to get nav/sidebar/breadcrumb context. Falls back to a synthesized “orphan” entry for pages on disk but not in nav.

  3. OpenAPI handling

    If frontmatter has openapi: METHOD path or the page is a synth endpoint, fetches the spec and renders via <OpenApiEndpoint> (or a third-party viewer if api.viewer is scalar / redoc / stoplight).

  4. Render

    Wraps content in Layout + PageShell, passes mdxComponents into <Content components={...} />. The Tangly Astro integration has already aliased every component import so user overrides at <userRoot>/theme/<Name>.astro intercept transparently.

Content collection

ts
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 eject materializes this directory into the user’s repo and removes tangly from 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.

Last updated Edit this page
↑↓ navigate open esc close