Skip to content

Add server-rendered Markdown partials via <markdown> and [markdown] #12

@tormnator

Description

@tormnator

Summary

Nue currently supports:

  • full page Markdown rendering through Nuemark (.md pages)
  • inline Markdown string rendering through the markdown(...) helper in HTML templates

What is missing is a content-first way to include a reusable Markdown snippet inside either:

  • an HTML layout/template
  • another Nuemark document

This issue proposes a Markdown Partial feature using a shared tag name in both renderers:

<markdown src="./partials/intro.md" />
[markdown src=./partials/intro.md]

The goal is to make reusable Markdown snippets first-class server-rendered partials, without trying to make .md files behave like full HTML-based Nue components.

Why This Feature

This feature is motivated by a practical authoring gap.

Today, if content needs a heading or richer block structure inside YAML or inside reused snippets, the available options are limited:

  • keep the content inline in HTML
  • pass strings through markdown(...)
  • use raw HTML in data files
  • promote the content into a full page-level .md file

Those options work, but they are awkward for reusable content blocks that are still fundamentally Markdown/content rather than UI runtime components.

A Markdown Partial feature would let users keep content in Markdown files and include it where needed without forcing that content into either:

  • YAML multiline edge cases
  • raw HTML fragments
  • HTML component libraries

Preferred API

Use the tag name markdown in both HTML and Nuemark.

In HTML templates/layouts

<markdown src="./partials/intro.md" />

<markdown src="./partials/callout.md" title="Fast path" tone="note" />

In Nuemark content

[markdown src=./partials/intro.md]
[markdown]
  src: ./partials/callout.md
  title: Fast path
  tone: note

This mirrors existing paired concepts like <svg> and [svg], keeps the API short, and fits Nue's existing tag-oriented mental model.

Proposed Behavior

Core semantics

  • markdown renders a referenced .md file as a server-rendered partial.
  • It is not a new full Nue component type.
  • It does not become a client-mounted/hydrated component.
  • It returns rendered HTML at build/render time.
  • It should work both in HTML templates and in Nuemark documents.

File type scope

For v1, only .md targets should be supported.

If broader include semantics are wanted later, they should be handled as a separate feature rather than folded into the first implementation.

Path resolution

  • Relative src values resolve relative to the file containing the markdown tag.
  • Absolute-like paths such as /docs/intro.md resolve from site root.
  • Nested partials resolve relative to the partial file that contains the nested include.
  • Escaping outside the site root should be rejected.

Data model

The most useful precedence order appears to be:

  1. outer page/template data
  2. partial front matter
  3. include-site overrides from tag attributes or YAML tag body

That gives partial files sane defaults while keeping include sites explicit and overridable.

Example:

<markdown src="./partials/callout.md" tone="warning" />

If callout.md contains front matter like:

---
tone: note
title: Heads up
---

then inside the partial:

  • title resolves to Heads up
  • tone resolves to warning
  • all ordinary outer page/template data is still available as base context

Metadata behavior

Partial front matter should be local to the partial render only.

It should not modify outer page-level values such as:

  • page title
  • page description
  • outer url
  • outer slug
  • outer parsed headings
  • page-level metadata used by layouts, collections, feed generation, etc.

In other words, the partial render gets a local merged context, but the outer page remains authoritative for page/document metadata.

Nested partials

Nested partials should be supported.

This is important because once the feature exists, users will naturally want to compose reusable callouts, intros, footnotes, etc.

Cycle handling

Cycles should fail clearly and early, for example:

  • a.md includes b.md
  • b.md includes a.md

The error should report the include chain so users can fix it quickly.

Why This Should Be a Markdown Partial, Not a Full Nue Component

This feature solves a similar authoring need as HTML components, but it should not try to be the same abstraction.

Current architecture distinction

HTML-based Nue components are AST-based and can participate in:

  • server-side rendering
  • client mounting/hydration
  • HTML library discovery
  • JS module generation/import for runtime mounting

Markdown does not currently participate in that model. It renders to HTML strings through Nuemark.

Trying to make .md files into full Nue components would likely require a second component model or a new compilation target.

That would add substantially more complexity than the core use case justifies.

Better framing

This feature fits better as:

  • reusable content partials
  • server-rendered snippets
  • content composition

rather than:

  • interactive/runtime components
  • mountable islands
  • HTML AST component libraries

Suggested Implementation Direction

This appears best implemented primarily in Nuekit, not as a new fundamental Nuemark component model.

HTML path

HTML rendering already has a built-in hook for tag-specific render functions.

That makes <markdown ... /> a good fit as a built-in render function, rather than as a new compiled HTML component type.

Nuemark path

Nuemark rendering already supports tag renderers.

That makes [markdown ...] a good fit as a built-in Nuemark tag resolved from the same page-scoped partial renderer.

Shared renderer

The cleanest implementation is likely a shared page-scoped partial rendering helper/factory that both the HTML and Nuemark render paths can call.

That helper would:

  • resolve the referenced asset
  • parse the referenced Markdown file
  • merge local render data
  • render the partial through Nuemark
  • support nested partial includes
  • track include ancestry for cycle detection
  • register dependency edges for rebuild/HMR

Likely Implementation Surface

packages/nuekit/src/render/page.js

Primary orchestration point.

Likely responsibilities:

  • inject built-in <markdown> render function for HTML rendering
  • inject built-in [markdown] tag renderer for Nuemark rendering
  • share one page-scoped partial renderer between both paths
  • reuse current page data and current Nuemark tag/component context

packages/nuekit/src/asset.js

Likely responsibilities:

  • resolve related assets by path
  • support relative and root-based markdown partial lookup
  • track explicit partial dependencies outside the normal convention-based dependency model
  • maintain ancestry/include chain for cycle detection

packages/nuekit/src/site.js and/or dev server invalidation flow

Likely responsibilities:

  • support invalidation/re-render of parent pages when a referenced partial changes
  • ensure dev-server updates affect the correct pages

packages/nuedom/src/dom/node.js

Possibly needed.

HTML built-in render functions currently receive merged data from:

  • evaluated attributes
  • outer template data

If attribute precedence is wrong for this use case, this merge order may need adjustment so include-site overrides reliably win over inherited outer data.

This is worth checking carefully because the expected precedence for partial includes is that explicit include-site overrides win.

Tests

Likely test files:

  • packages/nuekit/test/render/md.test.js
  • packages/nuekit/test/render/asset-render.test.js
  • possibly dev-server/HMR-focused tests if current harness supports it

Acceptance Criteria

Rendering

  • An HTML page can render <markdown src="./partial.md" />.
  • A Nuemark page can render [markdown src=./partial.md].
  • Output is rendered HTML, not a client stub.
  • Nested partials render correctly.

Resolution and safety

  • Relative resolution works from the containing file.
  • Root-based resolution works from site root.
  • Non-.md targets are rejected in v1.
  • Cycles are rejected with a useful error message.
  • Paths outside the site root are rejected.

Data behavior

  • Outer page/template data is available inside the partial.
  • Partial front matter is merged locally.
  • Include-site overrides win over partial front matter and outer data.
  • Partial metadata does not mutate outer page metadata.

Integration

  • Partials can use the same Nuemark custom tags available to the page.
  • Changes to a partial invalidate/re-render affected pages in dev mode.
  • Build output is correct without requiring client-side execution.

Complexity

Estimated implementation complexity: medium-high.

Why not low

This is more than a small helper because it crosses:

  • HTML rendering
  • Nuemark rendering
  • file resolution
  • dependency tracking
  • dev invalidation/HMR behavior
  • partial-local data precedence

Why not extremely high

The feature still aligns with existing extension points:

  • HTML built-in render functions already exist
  • Nuemark tag renderers already exist
  • Markdown parsing/rendering already exists
  • Nuekit already owns site/file/asset context

The feature does not require inventing a second full component runtime if kept scoped to server-rendered partials.

Main Risks

1. Data precedence confusion

The most immediate behavioral risk is ambiguous precedence between:

  • outer page data
  • partial front matter
  • explicit include-site overrides

If that order is not defined and tested carefully, users will get surprising results.

2. Dependency invalidation gaps

Nue's normal dependency system is convention-based. Markdown partials introduce explicit, file-level include edges.

If those edges are not tracked, then:

  • dev updates may not refresh parent pages correctly
  • incremental builds may miss affected parents

3. Cycle handling / recursive rendering

Once nested partials are allowed, cycle protection becomes mandatory.

Without explicit ancestry tracking, recursive includes can fail badly or produce opaque errors.

4. Scope creep into full component behavior

It will be tempting to extend this into:

  • interactive markdown components
  • slot behavior
    n- mountable client islands
  • generic include semantics for many file types

That should be resisted in the first implementation. The value of this feature is that it stays content-first and server-rendered.

5. Interaction with existing component/template data expectations

Depending on how HTML built-in render functions receive data, there may be overlap with the broader question of how inline HTML constructs see outer template data.

This should be handled narrowly for markdown partials rather than using the feature as implicit justification for broader component-data behavior changes.

Non-Goals for v1

  • making .md files full Nue components
  • client hydration / mount support for markdown partials
  • generic partial support for arbitrary file types
  • slot APIs for markdown partials
  • executing JS runtime behavior from markdown partials

Recommendation

Proceed with this feature as a Markdown Partial implementation with:

  • <markdown> in HTML
  • [markdown] in Nuemark
  • server-rendered only
  • .md targets only in v1
  • explicit dependency tracking
  • strong tests around precedence, nesting, and invalidation

That gives users a meaningful content-composition feature without forcing Markdown into the existing HTML component runtime model.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions