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
/samplesis a second@docusaurus/plugin-content-docsinstance (id: 'userGuide'), configured indocusaurus.config.jsalongside the maindocspreset — not a page under the main docs sidebar.- Content lives in
samples/at the repo root:samples/index.mdxis the generic/sampleslanding page, and each guide lives in its own sibling folder of.mdxfiles —samples/clearpath-roster-guide/andsamples/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.jsin the repo root (the main site's sidebar issidebars.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 inthemeConfig.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 ownitems. Pages don't have to be nested inside a category — a bare string can sit as a sibling of category objects in the sameitemsarray. 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.
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.:
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:
---
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.
Sidebar structure
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:
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'shrefin the static HTML purely as a no-JS fallback (useCategoryHrefWithSSRFallbackin@docusaurus/theme-classic); once the page hydrates, clicking a heading without an explicitlinkonly expands/collapses it. Giving the top "Roster generation guide" category an explicitlinkto 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 thelinkand listingclearpath-roster-guide/01-overviewas 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:
{
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.
Sidebar file locations
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:
sidebars-user-guide.js(repo root) →samples/sidebars.js— colocated with the content it configures, since asidebars.jsfile is declarative config data for a docs instance, not an implementation a plugin runs (unlike the files inplugins/, which are actual plugin code).- Both
sidebars.js(root) andsamples/sidebars.js→src/js/sidebars-docs.jsandsrc/js/sidebars-samples.js— gathered into a dedicatedsrc/js/directory instead of scattering one per content folder, with filenames disambiguating the two since they now share a directory. - Both files → back to the repo root as
sidebars.jsandsidebarsSamples.js— the Docusaurus convention. Sidebar files are build-time config read bydocusaurus.config.js, not client code, so they don't belong insrc/(which holds code bundled for the browser), and ajs/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.
Fixing cross-reference links
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.