Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/nuejs/nue/llms.txt

Use this file to discover all available pages before exploring further.

Nue separates content from configuration from data. Pages are written in Markdown or HTML. Configuration lives in YAML files. Datasets live in their own YAML or JSON files that get merged into the page’s data context at render time. This separation means a marketing writer can update copy, a developer can change page structure, and a data analyst can update charts — all without touching each other’s files.

YAML data files

Any .yaml file in your application directory is loaded as data and merged into the page context. Keys that don’t match reserved configuration properties (include, exclude, meta, content, import_map, svg) become available as template variables in your layouts and components.
# blog/data.yaml
site_name: "The Nue Blog"
twitter: "@nuejs"
featured_posts:
  - title: "Building fast sites"
    url: /blog/fast-sites
  - title: "Content-first development"
    url: /blog/content-first
These values are then available in any Nue HTML template in the same application:
<site-header>
  <a href="/">{ site_name }</a>
  <a href="https://twitter.com/{ twitter }">Twitter</a>
</site-header>

app.yaml for per-application configuration

Each application directory can have an app.yaml file that configures the layout and behavior for that section of the site. It merges with the global site.yaml using mergeConf, where array values like include and exclude are fully replaced (not appended), and object values like meta and content are shallow-merged:
# blog/app.yaml
include:
  - blog.css
  - blog.js

content:
  heading_ids: true
  sections: true

meta:
  og:type: article
  twitter:card: summary_large_image
The include array tells Nue which CSS and JS files to load for pages in this application. content.heading_ids adds id attributes to headings for anchor links. content.sections wraps ----separated Nuemark blocks in <section> elements.
app.yaml configuration is resolved per-page at render time by traversing the asset’s dependency graph to find the nearest app.yaml. A page in blog/posts/ inherits the blog/app.yaml settings.

@shared/data/ for shared datasets

Data files in @shared/data/ are available to all applications in the site. They are loaded by mergeSharedData in site.js and merged into every page’s data context:
// From site.js
export async function mergeSharedData(assets, data={}) {
  const shared = assets.filter(a => a.dir?.startsWith(`@shared${sep}data`))
  const statics = shared.filter(f => f.is_json || f.is_yaml)

  const dataset = await Promise.all(statics.map(f => f.parse()))
  dataset.forEach(more => Object.assign(data, more))

  // modifier scripts
  const mods = shared.filter(f => (f.is_js || f.is_ts) && !f.name?.endsWith('.test'))
  for (const mod of mods) {
    const fns = await mod.parse()
    await fns.default?.(data)
  }

  return data
}
A typical shared data directory might look like:
@shared/
└── data/
    ├── nav.yaml        ← site-wide navigation links
    ├── authors.yaml    ← author profiles
    └── transform.js    ← modifier script
# @shared/data/nav.yaml
nav:
  - label: Docs
    href: /docs/
  - label: Blog
    href: /blog/
  - label: About
    href: /about

Content collections

Content collections let you query a set of Markdown files and present them as structured data — for blog indexes, documentation listings, or any page that aggregates content from multiple files. Collections are defined in site.yaml or app.yaml under the collections key. Each entry maps a collection name to a configuration object:
# site.yaml
collections:
  posts:
    include:
      - blog/
    require:
      - title
      - date
    sort: "date desc"

  tutorials:
    include:
      - docs/tutorials/
    tags:
      - tutorial
    sort: "title asc"
The collection name (e.g., posts) becomes a key in the page data context, containing an array of page objects ready to iterate over in templates.

The getCollections API

The getCollections function in collections.js processes collection definitions against the full list of site files:
// From collections.js
export async function getCollections(files, opts) {
  const arr = {}
  for (const [name, conf] of Object.entries(opts)) {
    arr[name] = await createCollection(files, conf)
  }
  return arr
}

export async function createCollection(files, conf) {
  let matchedPages = matchPages(files, conf.include)
  let filteredPages = await filterPages(matchedPages, conf)
  return sortPages(filteredPages, conf.sort)
}

include

The include array specifies path patterns. Any file whose path contains one of the patterns is included as a candidate:
function matchPages(files, patterns=[]) {
  const ret = []
  for (const pattern of patterns) {
    for (const page of files) {
      if (page.path.includes(pattern)) ret.push(page)
    }
  }
  return ret
}
Use directory paths like blog/ to include all posts, or more specific paths like blog/2025/ to include only one year’s posts.

require

require filters out any page that is missing one or more specified front matter fields. This ensures collection items always have the metadata your template depends on:
collections:
  posts:
    include: [blog/]
    require: [title, date, author]
A Markdown file without a date in its front matter will not appear in the posts collection.

tags

tags filters to pages whose front matter tags array contains at least one of the specified values:
collections:
  tutorials:
    include: [docs/]
    tags: [tutorial, guide]
// From collections.js
if (conf.tags && !conf.tags.some(tag => meta.tags?.includes(tag))) continue

skip

skip excludes pages where any of the specified front matter fields is truthy. Use this to exclude draft posts, private pages, or featured items that appear elsewhere:
collections:
  posts:
    include: [blog/]
    skip: [draft, featured]
A post with draft: true or featured: true in its front matter will not appear in the listing.

sort

sort is a string with the field name and optional direction (asc or desc). The default direction is ascending:
sort: "date desc"        # newest first
sort: "title asc"        # alphabetical
sort: "order"            # by explicit order field, ascending
// From collections.js
function sortPages(files, sorting) {
  if (!sorting) return files
  const [field, direction = 'asc'] = sorting.split(' ')

  return files.toSorted((a, b) => {
    const aVal = a[field]
    const bVal = b[field]
    const result = aVal < bVal ? -1 : aVal > bVal ? 1 : 0
    return direction == 'desc' ? -result : result
  })
}

Collection items

Each item in a collection array contains all front matter fields from the source Markdown file, plus three routing properties added by the system:
PropertyDescription
urlThe page’s URL path (e.g., /blog/hello-world)
dirThe directory containing the file (e.g., blog)
slugThe filename without extension (e.g., hello-world)
Use these in your listing templates:
<blog-index>
  <article :for="post in posts">
    <time>{ post.date }</time>
    <a href="{ post.url }">{ post.title }</a>
    <p>{ post.description }</p>
    <span :for="tag in post.tags" class="tag">{ tag }</span>
  </article>
</blog-index>

Modifier scripts

Modifier scripts are JavaScript or TypeScript files in @shared/data/ (files not ending in .test). They export a default async function that receives the merged data object and can transform it in place. This is where you add computed properties, filter arrays, or fetch external data.
// @shared/data/transform.js
export default async function(data) {
  // Add computed totals
  if (data.posts) {
    data.post_count = data.posts.length
    data.recent_posts = data.posts.slice(0, 3)
  }

  // Enrich author data
  if (data.authors && data.posts) {
    data.posts = data.posts.map(post => ({
      ...post,
      author_info: data.authors.find(a => a.slug === post.author)
    }))
  }
}
Multiple modifier scripts in @shared/data/ run in sequence. Files are loaded in filesystem order, so name them with numeric prefixes if order matters:
@shared/data/
├── 01-fetch.js      ← fetch external data
├── 02-transform.js  ← transform fetched data
└── 03-computed.js   ← add computed properties
Modifier scripts run at build time (or request time in development). Avoid side effects like writing files or making mutations that should only happen once — the script may run multiple times during a hot-reload session.

Full data flow example

Here is how all data sources combine for a blog post:
1

Site-wide data loads first

site.yaml provides global configuration. @shared/data/*.yaml files add shared datasets (navigation, authors, etc.).
2

Application data merges in

blog/app.yaml and any other .yaml files in blog/ merge their non-configuration keys into the page data context.
3

Front matter from the page itself

The Markdown file’s front matter (title, date, author, tags) is merged last, so page-level values take priority over inherited ones.
4

Collections resolve

If collections is configured, Nue queries matching Markdown files, applies require/tags/skip filters, sorts results, and adds the arrays to the data context.
5

Modifier scripts run

Any .js/.ts files in @shared/data/ run as transforms, allowing computed properties and external data to be added before the page renders.
The final merged data object is passed to both the Nuemark renderer (for content tags) and the Nuedom renderer (for layout components), giving every part of the page access to the same dataset.

Build docs developers (and LLMs) love