Templates

Templates are JSX/TSX components that receive a page object and render it. Each content file maps to a template by filename convention.

Template props

Every template receives TemplateProps:

import type { TemplateProps } from "dune/types";
import StaticLayout from "../components/layout.tsx";

export default function PostTemplate({ page, pageTitle, site, config, nav, Layout, children }: TemplateProps) {
  const LayoutComponent = Layout ?? StaticLayout;
  return (
    <LayoutComponent site={site} config={config} nav={nav} page={page} pageTitle={pageTitle}>
      <article>
        <h1>{page.frontmatter.title}</h1>

        <time datetime={page.frontmatter.date}>
          {new Date(page.frontmatter.date).toLocaleDateString()}
        </time>

        <div>{children}</div>

        {page.frontmatter.taxonomy?.tag?.map((tag) => (
          <a key={tag} href={`/tag/${tag}`}>{tag}</a>
        ))}
      </article>
    </LayoutComponent>
  );
}

What's in TemplateProps

Prop Type Description
page Page The full page object (frontmatter, content, media, relations)
pageTitle string Pre-formatted title: "Title - Descriptor | Site Name"
site SiteConfig Site configuration (title, URL, metadata)
config DuneConfig Full merged configuration
nav PageIndex[] Top-level navigation pages
Layout Component? Dynamically loaded layout component (see below)
collection Collection? Collection results if page defines one
children Element? Pre-rendered content (HTML wrapped in a div)
searchQuery string? Set when rendering the /search page. The raw query string from ?q=.
searchResults SearchResult[]? Set when rendering the /search page. Ranked results from the search engine.

Using the Layout prop

The router passes a Layout prop that is loaded dynamically on each request. This ensures layout changes take effect during development without restarting the server. Templates should prefer the Layout prop over a static import:

import StaticLayout from "../components/layout.tsx";

export default function MyTemplate({ Layout, ...props }: TemplateProps) {
  // Layout prop is fresh on every request; StaticLayout is the build-time fallback
  const LayoutComponent = Layout ?? StaticLayout;
  return (
    <LayoutComponent {...props}>
      {/* content */}
    </LayoutComponent>
  );
}

If a template only uses import Layout from "../components/layout.tsx" directly, layout file changes won't appear until the server restarts. Dune logs a warning when it detects this pattern during development.

What's in Page

Property Type Description
page.frontmatter PageFrontmatter All frontmatter fields
page.route string URL path: /blog/hello-world
page.format ContentFormat "md", "tsx", or "mdx"
page.template string Template name: "post"
page.media MediaFile[] Co-located media files
page.html() Promise<string> Rendered HTML (Markdown pages)
page.summary() Promise<string> Auto-generated excerpt
page.children() Promise<Page[]> Child pages
page.parent() Promise<Page|null> Parent page
page.siblings() Promise<Page[]> Sibling pages

Note: html(), children(), parent(), and siblings() are lazy — they only load data when called.

Template naming convention

Content file Template used
default.md templates/default.tsx
post.md templates/post.tsx
blog.md templates/blog.tsx
item.md templates/item.tsx

Override with the template frontmatter field:

template: landing   # uses templates/landing.tsx instead

Reserved template names

The following template names are used by Dune's built-in routes:

Template Route When rendered
search /search When a visitor submits a search query. Receives searchQuery and searchResults in props. If not present, Dune falls back to a built-in standalone page.

See Search for a full example.

Blog listing template example

import StaticLayout from "../components/layout.tsx";

export default function BlogTemplate({ page, pageTitle, site, config, nav, Layout, collection, children }: TemplateProps) {
  const LayoutComponent = Layout ?? StaticLayout;
  return (
    <LayoutComponent site={site} config={config} nav={nav} page={page} pageTitle={pageTitle}>
      <h1>{page.frontmatter.title}</h1>

      <div>{children}</div>

      {collection && (
        <ul>
          {collection.items.map((post) => (
            <li key={post.route}>
              <a href={post.route}>
                <h2>{post.frontmatter.title}</h2>
                <time>{post.frontmatter.date}</time>
              </a>
            </li>
          ))}
        </ul>
      )}

      {collection?.hasNext && (
        <a href={`${page.route}/page:${collection.page + 1}`}>
          Older posts →
        </a>
      )}
    </LayoutComponent>
  );
}