7Sense docs

Authoring with Markdoc

Frontmatter, the tag schema, and how a page becomes a route.

A page#

Every .md file under pages/ is a route. Frontmatter drives the document title, the meta description and the page heading:

---
title: Getting started
description: Install, run the dev server, and produce the static bundle.
---

pages/_app.tsx reads it from pageProps.markdoc.frontmatter and hands it to the layout.

Tags#

Tags are the part Markdown does not have. They are declared in markdoc/tags/index.js, where render points at the imported React component — the plugin resolves it at build time, so there is no string-to-component map that can drift:

import { Callout } from "../../components/Callout";

export const callout = {
  render: Callout,
  attributes: {
    type: { type: String, default: "note", matches: ["note", "warning", "danger"] },
    title: { type: String },
  },
};

Used as:


Which renders:

The content gate#

@markdoc/next.js parses and transforms your content. It never calls Markdoc.validate, so on its own the schema is decoration. Measured on this repo before the gate existed: a page with {% callout type="explosive" %} and an entirely undefined tag built with exit 0 and shipped class="callout callout--explosive" into the HTML. errorLevel: "critical" did nothing, because nothing was reading it.

scripts/validate-content.mjs is what reads it. It runs Markdoc.validate over every page, checks that each one has non-empty title and description frontmatter, and checks that every sidebar link resolves to a real route. It is wired into pnpm build, so it guards the deploy too:

✗ pages/docs/getting-started.md:43 — [critical] attribute-value-invalid: Attribute 'type' must
  match one of ["note","warning","danger"]. Got 'explosive' instead.
✗ pages/docs/getting-started.md:47 — [critical] tag-undefined: Undefined tag: 'totally_unknown_tag'
validated 4 page(s), 4 route(s); 2 blocking problem(s)

Run it alone with pnpm validate:content.

Nodes#

Nodes override the built-in Markdown elements. markdoc/nodes/index.js overrides heading to slugify an id and attach an anchor link, so any section is addressable.

Adding a component#

  1. Write the component under components/.
  2. Declare the tag in markdoc/tags/index.js with its attributes and errorLevel.
  3. Link the page in components/nav.ts — the gate fails if a nav link points at a route no page exports, and a page absent from the nav is reachable only by URL.