Skip to main content

The /samples section

How the /samples demo area was built: a second, standalone docs instance that hosts a sample product user guide, reachable only from a link on /portfolio and deliberately left out of the navbar.

Quick reference

  • /samples is a second @docusaurus/plugin-content-docs instance (id: 'userGuide'), configured in docusaurus.config.js alongside the main docs preset — not a page under the main docs sidebar.
  • Content lives in samples/ at the repo root: samples/index.mdx is the generic /samples landing page, and each guide lives in its own sibling folder of .mdx files — samples/clearpath-roster-guide/ and samples/clearpath-collect-guide/.
  • samples/norway-data-api-guides/ holds real developer guides for the live Norway Data API, not genericized samples. See The tutorial and guides.
  • Its sidebar is hand-written at sidebarsSamples.js in the repo root (the main site's sidebar is sidebars.js, also in the root; see Sidebar file locations below).
  • It's reachable only via the "Product user guide" link on /portfolio — nothing registers it in themeConfig.navbar.
  • Direct link vs. category: a doc id string in items (e.g. 'clearpath-roster-guide/get-started') is just a sidebar row that links straight to that page. A { type: 'category', ... } object is a separate, expandable heading with its own items. Pages don't have to be nested inside a category — a bare string can sit as a sibling of category objects in the same items array. See Mixing pages and categories below.

Why a second docs instance​

The brief was to demo a product user guide that looks and behaves like the main /docs section (sidebar, categories, landing page) but isn't part of this site's real documentation and shouldn't appear in the navbar. Docusaurus's docs plugin supports exactly this via multiple instances: the classic preset's docs option configures the default instance (/docs), and a second, explicitly-configured @docusaurus/plugin-content-docs entry in plugins configures an independent one with its own content folder, route, and sidebar.

docusaurus.config.js
plugins: [
blogSeriesPlugin,
[
'@docusaurus/plugin-content-docs',
{
id: 'userGuide',
path: 'samples',
routeBasePath: 'samples',
sidebarPath: './sidebarsSamples.js',
remarkPlugins: [remarkImageSize],
editUrl: undefined,
numberPrefixParser: false,
},
],
],

Order in the plugins array is just array order, not a hierarchy — blogSeriesPlugin and this docs instance are unrelated and don't nest.

Content layout​

samples/
├── index.mdx # generic /samples landing page (slug: /)
├── clearpath-roster-guide/
│ ├── 01-overview.mdx
│ ├── 02-about-delegated-authority.mdx
│ ├── ...
│ └── assets/
└── clearpath-collect-guide/
├── 01-overview.mdx
├── 02-how-it-works.mdx
└── ...

path: 'samples' makes samples/ the content root for this instance, so samples/index.mdx can serve as the root landing page while each guide lives in its own sibling folder. samples/clearpath-collect-guide/ is the second guide, added this way: drop its files in a new folder, link it from samples/index.mdx, and give it its own top-level category in the sidebar (see below) — the hub page was never tied to just the one guide.

Unlike clearpath-roster-guide/, which reuses plain Markdown tables and admonitions throughout, clearpath-collect-guide/ demos screen interactions (home-page tiles, record-entry forms, lookup-list setup) with small React components from src/components/ClearpathCollectMockups/ — real HTML/CSS, not screenshots or traced SVG, so every label is editable text at a readable size. They follow the same CSS-variable convention (light/dark tokens) as the figure components elsewhere in /portfolio, e.g. RosterUploadDiagrams.

Why numberPrefixParser: false​

clearpath-collect-guide/ and contributor-guide/'s source files (exports of help-center articles) use numbered filenames — 01-overview.mdx, 02-how-it-works.mdx, etc. — and their own cross-links reference those exact filenames, e.g.:

samples/clearpath-collect-guide/01-overview.mdx
If you work with one or more carriers in a
[delegated relationship](./02-how-it-works), ...

Docusaurus's default numberPrefixParser strips that numeric prefix when computing each doc's id/slug, which breaks links written against the un-stripped filename. Rather than rewrite those internal links, this instance disables that parser, so doc ids keep their numbers (e.g. clearpath-collect-guide/02-how-it-works) and the links resolve as authored.

clearpath-roster-guide/ doesn't have this problem: its source filenames had their numeric prefixes stripped (01-get-started.mdx → get-started.mdx, etc.) and its internal cross-links were rewritten to match, so its doc ids are unnumbered even though numberPrefixParser: false applies to the whole instance.

The landing page​

samples/index.mdx sets slug: /, which tells this docs instance to serve that doc at the root of its routeBasePath (/samples) instead of at a path derived from its filename:

samples/index.mdx
---
title: "Samples"
slug: /
---

# Samples

Sample documentation, written to demonstrate structure and style rather than a real product.

- [Clearpath Roster User Guide](/samples/clearpath-roster-guide/get-started) - ...

This mirrors how docs/overview.mdx works for the main /docs instance, but as its own file rather than reusing the first guide page as the landing page — the guide's own 01-overview.mdx still exists as that guide's first article, listed normally in the sidebar.

The sidebar for this instance is hand-written, not autogenerated, because the guide's section: frontmatter (inherited from the original help-center export) groups its articles into categories that don't match the flat filename order:

sidebarsSamples.js
const sidebars = {
userGuideSidebar: [
'index',
{
type: 'category',
label: 'Roster generation guide',
collapsed: false,
// No `link` - the heading is a pure toggle, not a page of its own.
items: [
{
type: 'category',
label: 'Get started',
collapsed: false,
items: [
'clearpath-roster-guide/01-overview',
'clearpath-roster-guide/02-about-delegated-authority',
// ...
],
},
// Generate a roster / Create a template / Design a template's layout
],
},
],
};

Two things worth calling out:

  • 'index' is listed first, so the generic landing page shows up as its own row in the sidebar, above the guide.
  • Categories have no link. Docusaurus renders a category heading's href in the static HTML purely as a no-JS fallback (useCategoryHrefWithSSRFallback in @docusaurus/theme-classic); once the page hydrates, clicking a heading without an explicit link only expands/collapses it. Giving the top "Roster generation guide" category an explicit link to the overview doc made that heading behave like a real link instead — which also hid the overview page as a listed row, since it was only reachable by clicking the heading. Removing the link and listing clearpath-roster-guide/01-overview as the first item under "Get started" fixed both.

Add a second guide by giving it its own top-level category here, as a sibling of "Roster generation guide".

Mixing pages and categories​

items is just an array — Docusaurus doesn't require every entry to be wrapped in a category. A plain doc id string and a { type: 'category', ... } object can sit side by side at the same nesting level, and the string renders as a normal clickable row with no heading of its own:

sidebarsSamples.js
{
type: 'category',
label: 'Clearpath Roster User Guide',
collapsed: true,
items: [
// A bare string: links straight to the page, no sub-heading.
'clearpath-roster-guide/get-started',
// A category object: its own expandable heading with nested items.
{
type: 'category',
label: 'Create a template',
collapsed: true,
items: [
'clearpath-roster-guide/create-a-template',
'clearpath-roster-guide/choose-a-relationship-type',
],
},
],
},

This is the same mechanism used for 'index' at the top of userGuideSidebar above — it just also works one level deeper, inside a guide's own category. Use a bare string when a page doesn't need to group any other pages under it; reach for a category only once you have two or more pages that belong together under a shared heading.

Both this site's sidebar config files started out as single root-level files (sidebars.js, matching the Docusaurus scaffold default) and moved twice before returning to the root:

  1. sidebars-user-guide.js (repo root) → samples/sidebars.js — colocated with the content it configures, since a sidebars.js file is declarative config data for a docs instance, not an implementation a plugin runs (unlike the files in plugins/, which are actual plugin code).
  2. Both sidebars.js (root) and samples/sidebars.js → src/js/sidebars-docs.js and src/js/sidebars-samples.js — gathered into a dedicated src/js/ directory instead of scattering one per content folder, with filenames disambiguating the two since they now share a directory.
  3. Both files → back to the repo root as sidebars.js and sidebarsSamples.js — the Docusaurus convention. Sidebar files are build-time config read by docusaurus.config.js, not client code, so they don't belong in src/ (which holds code bundled for the browser), and a js/ folder grouped them by file type rather than purpose.

Each move required updating the corresponding sidebarPath in docusaurus.config.js (presets[0][1].docs.sidebarPath for the main instance, the userGuide plugin entry's sidebarPath for this one) — the doc ids inside each sidebar file didn't need to change, since those are relative to the content path, not the sidebar file's own location.

Two existing links elsewhere on the site pointed at this section's URL before it settled on /samples:

  • src/pages/portfolio.mdx — the "Product user guide" link, updated from /samples/roster (an early, never-built route) to /samples.
  • docs/styling/icons.mdx — a reference to "the roster samples", updated from ../../samples/roster/ to ../../samples/.

onBrokenLinks: 'throw' in docusaurus.config.js catches mismatches like these at build time, which is how both were found.