@booleanpress/nimbus
v0.2.1
Published
Reusable BooleanPress documentation layout and components for Astro and Nimbus.
Maintainers
Readme
@booleanpress/nimbus
A reusable Astro + Nimbus documentation shell. It supplies the header, search UI, sidebars, article layout, table of contents, feedback, footer, styles, and MDX components. The site supplies its own content, navigation tree, logos, and links. The visible components and styles began with the MIT-licensed Nimbus starter; see NOTICE and LICENSE.
Requirements
Use Node 22.14 or newer and install these directly in the consuming site's root package.json: @booleanpress/nimbus, @cloudflare/nimbus-docs 0.14.2, Astro 7, @iconify-json/ph, Tailwind 4, and @tailwindcss/vite. Nimbus discovers the Phosphor icon collection from the site's manifest; the package's peer dependency alone does not make the icons available to Nimbus. astro-og-canvas is a package dependency used by the reusable OG routes. Install @astrojs/mdx only if your content uses MDX, following Nimbus's setup for that content type.
Minimal site wiring
The examples/basic directory in this package is a complete starting site. Its nimbus.json records the reviewed Nimbus 0.14.2 upgrade baseline; keep that file and follow Nimbus's migration review when upgrading Nimbus. The key files are shown below.
Configure Nimbus and Tailwind in astro.config.mjs:
import nimbus, { defineConfig as defineNimbusConfig } from '@cloudflare/nimbus-docs';
import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'astro/config';
const docs = defineNimbusConfig({
site: 'https://docs.example.com',
title: 'Example documentation',
description: 'Guides for Example.',
socialImage: '/og.png',
socialImageAlt: 'Example documentation preview',
sidebar: {
items: [
{ label: 'Guides', autogenerate: { directory: 'guides' } },
{ label: 'Blog', autogenerate: { directory: 'blog' } },
],
},
});
export default defineConfig({
site: docs.site,
trailingSlash: 'always',
output: 'static',
vite: { plugins: [tailwindcss()] },
integrations: [nimbus(docs)],
});Register the Nimbus docs collection in src/content.config.ts:
import { docsCollection } from '@cloudflare/nimbus-docs/content';
import { defineCollection } from 'astro:content';
import { seoSchemaFields } from '@booleanpress/nimbus/seo/schema';
export const collections = {
docs: defineCollection(docsCollection({ schemaFields: seoSchemaFields })),
};The package schema requires a non-empty description on each page and validates optional author, publication date, representative image, article type, and subject fields. Use seo.type: WebPage on an archive or profile page to avoid default article markup. It does not impose a character-count target.
Provide product branding in src/site.ts:
import type { BooleanPressSiteConfig } from '@booleanpress/nimbus/config';
import { booleansmtp } from '@booleanpress/brand';
export const siteConfig: BooleanPressSiteConfig = {
productName: 'Example',
seo: {
publisher: {
name: 'Example Docs',
url: 'https://docs.example.com/',
logo: { url: '/brand/publisher-mark.svg', width: 128, height: 128 },
},
about: { type: 'SoftwareApplication', name: 'Example' },
ogCard: {
bgGradient: [[24, 31, 54], [51, 38, 92]],
border: { color: [115, 92, 220], width: 10, side: 'inline-start' },
fonts: ['./public/brand/inter-bold.ttf'],
logo: { path: './public/brand/publisher-mark.svg', size: [80] },
format: 'PNG',
},
},
navigationLabel: 'Documentation',
brand: booleansmtp, // from '@booleanpress/brand': the product's logo, footer and colours
headerLinks: [{ label: 'Get started', href: '/guides/' }],
};Put the SEO publisher logo and any Open Graph font files in public/brand/; include the font's license when required. Use local fonts in ogCard.fonts so static image generation does not depend on a third-party font fetch. Navigation links honor Astro's base setting. The optional githubUrl and reportIssueUrl fields customize the remaining shell; chatLinks: false leaves "Ask GPT" and "Ask Claude" out of the page actions under each title ("Ask Agent" stays).
brand names the product the site belongs to, one object from @booleanpress/brand that every site of the product passes. It supplies the header logo (BrandHeader in its docs layout: the logo block as wide as the sidebar, then the search box, the section links and the buttons), the footer (BrandFooter, identical on every site of the product), and the accent colours (set as --bp-primary-* on <html>). Without it, the header shows productName as text and the footer is a single copyright line.
page sets the width of mode: custom pages and of the footer (with productHeader, of the whole page): { max: '81.25rem', gutter: '1.25rem' } gives a 1300px box with 20px gutters (defaults: 72rem, 1rem / 1.5rem from 64rem). page.sidebar sets the sidebar's width (default 18.75rem); measure the widest titles with every group open before narrowing it.
productHeader: true is for docs served on the product's own domain (booleansmtp.com/docs/): the product's header (ProductHeader from @booleanpress/brand, with brand.nav) comes first and scrolls away; the docs bar under it stays, with navigationLabel, the search box starting where the page's text does, and headerLinks, GitHub and the theme toggle on the right. Both bars, the docs columns and the footer sit in the page box, so nothing moves between the product's site and its docs. defaultMode: 'light' keeps pages light until a visitor picks dark (the default, system, follows the operating system); the choice is stored in localStorage["ui-mode"], which a site on the same domain can share.
seo.publisher is required. Supply the actual publisher name, its canonical home page, and a public square logo at least 112×112 px owned by the consuming site; optional sameAs values must be official profiles. Set the site name in Nimbus's title; seo.about and OG card colors, fonts, and logo are also site inputs. The package generates the card routes but does not invent brand or article content.
Register the MDX components in src/components.ts:
import { Aside, Card, CardGrid, LinkCard, Step, Steps, TabItem, Tabs } from '@booleanpress/nimbus/components';
export const components = { Aside, Card, CardGrid, LinkCard, Step, Steps, TabItem, Tabs };Create src/pages/[...slug].astro using Nimbus route helpers and the package layout:
---
import DocsLayout from '@booleanpress/nimbus/layout';
import { components } from '../components';
import {
getDocsStaticPaths, getDocsPage, getRouteFlags, getSidebar, getPrevNext,
getBreadcrumbs, getEditUrl, getLastUpdated, getTOC, entryRouteKey, stripBase,
} from '@cloudflare/nimbus-docs/runtime';
import { siteConfig } from '../site';
export const prerender = true;
export const getStaticPaths = getDocsStaticPaths;
const page = await getDocsPage(Astro);
if (page instanceof Response) return page;
const { entry, Content, headings } = page;
const slug = stripBase(Astro.url.pathname, import.meta.env.BASE_URL).replace(/\/$/, '') || '/';
const { sidebar: sidebarOn, tableOfContents: tocOn } = await getRouteFlags(entry);
const sidebar = sidebarOn ? await getSidebar(slug, { collection: entry.collection }) : false;
const prevNext = await getPrevNext(slug, {
sidebarTree: sidebar === false ? [] : sidebar,
overrides: { prev: entry.data.prev, next: entry.data.next },
});
const breadcrumbs = await getBreadcrumbs(slug, { collection: entry.collection });
const editUrl = await getEditUrl(entry);
const lastUpdated = entry.data.lastUpdated ?? await getLastUpdated(entry);
const tocConfig = entry.data.tableOfContents;
const toc = tocOn && tocConfig !== false ? getTOC(headings, tocConfig) : false;
const routeKey = entryRouteKey(entry.id);
const markdownUrl = routeKey ? `/${routeKey}/index.md` : '/index.md';
const socialImage = entry.data.socialImage ?? `/og/${entry.id}.png`;
---
<DocsLayout
brandConfig={siteConfig}
title={entry.data.title}
description={entry.data.description}
sidebar={sidebar}
headings={toc}
breadcrumbs={breadcrumbs}
prevNext={prevNext}
mode={entry.data.mode}
banner={entry.data.banner}
head={entry.data.head}
searchable={entry.data.searchable}
noindex={entry.data.noindex}
markdownUrl={markdownUrl}
socialImage={socialImage}
lastUpdated={lastUpdated}
editUrl={editUrl}
draft={entry.data.draft}
seo={entry.data.seo}
collection={entry.collection}
entryId={entry.id}
>
<Content components={components} />
</DocsLayout>Add src/pages/search-index.json.ts so search works during astro dev:
export { prerender, GET } from '@booleanpress/nimbus/routes/search-index';Register the package's agent endpoints too. examples/basic/src/pages/ contains these small route files; for example, src/pages/[...slug]/index.md.ts is:
export { prerender, getStaticPaths, GET } from '@booleanpress/nimbus/routes/markdown';Its index.mdx.ts counterpart exports from /routes/source. The site-wide llms.txt.ts and llms-full.txt.ts export from /routes/llms and /routes/llms-full; [section]/llms.txt.ts exports from /routes/section-llms. These routes make Copy page, View as Markdown, and LLM index links usable in the consumer.
For a blog post, author and publication facts belong in frontmatter and should also be visible on the page:
seo:
type: BlogPosting
publishedAt: 2026-09-27
authors:
- type: Person
name: Jane Doe
url: https://docs.example.com/authors/jane-doe/
images:
- /blog/release-overview.jpg
about:
type: SoftwareApplication
name: ExampleThe layout displays authors and publication date, then emits matching BlogPosting JSON-LD with the publisher, canonical page, update date, representative image URLs, and subject. TechArticle is the default for documentation pages. Use only accurate metadata that matches visible content; article image URLs should be representative and crawlable.
Nimbus emits the WebSite site-name signal on the homepage from the configured Nimbus title; keep it concise and consistent with the visible site name. The package emits the publisher Organization node once on the homepage and refers to it from article markup. Structured data can make a page eligible for a search feature but never guarantees display or ranking. Google recommends validating with the Rich Results Test; use the Schema Markup Validator for broader Schema.org checks and Search Console URL Inspection after deployment. Google currently limits FAQ rich results to select authoritative sites and removed How-to rich results, so this package does not emit either type.
Add a robots route and per-page OG image route:
// src/pages/robots.txt.ts
import { config } from 'virtual:nimbus/config';
import { createRobotsTxtResponse } from '@booleanpress/nimbus/routes/robots';
export const prerender = true;
export const GET = () => createRobotsTxtResponse({ siteUrl: config.site, baseUrl: import.meta.env.BASE_URL });// src/pages/og/[...slug].ts
import { createOpenGraphImageRoutes } from '@booleanpress/nimbus/routes/open-graph';
import { siteConfig } from '../../site';
export const prerender = true;
export const { getStaticPaths, GET } = await createOpenGraphImageRoutes(siteConfig.seo.ogCard);For /og.png, call createHomeOpenGraphImageResponse() with the Nimbus site title, description, and the same site-owned ogCard options. Nimbus supplies canonical URLs, titles, descriptions, Open Graph/Twitter tags, version alternates, sitemap integration, baseline WebPage data, and homepage WebSite site-name markup. This package adds publisher identity, TechArticle/BlogPosting, and breadcrumb JSON-LD. Run bp-docs-seo-check --site https://docs.example.com after building to check metadata, schema, social images, robots, and sitemap output.
For Cloudflare static hosting, add this to public/_headers so generated Markdown files have the correct media type:
/*.md
Content-Type: text/markdown; charset=utf-8Other hosts should set the Markdown content type through their own static-asset or route configuration.
Production search uses the Pagefind index Nimbus generates during the build. This endpoint includes page bodies only in development; the built JSON includes public titles and descriptions. Create at least src/content/docs/index.md with title and description frontmatter before building.
Customize the shell
@booleanpress/nimbus/components exports components for MDX and named Aside, Card, CardGrid, LinkCard, Steps, Step, Tabs, and TabItem. It also exports Render for reusable MDX partials. Register a partials collection with Nimbus and add src/content/partials/ before using Render; a site that does not use it needs no partials collection. The package exports /base-layout, /header, and /footer for custom routes such as 404 pages.
The layout loads styles/globals.css and styles/prose.css, including self-hosted Inter and JetBrains Mono. Its default primary colors follow BooleanSMTP. For another brand, define these custom properties in site CSS loaded on the page; the package resolves them at use time, independent of CSS import order:
:root {
--bp-primary-light: oklch(0.47 0.18 240);
--bp-primary-hover-light: oklch(0.41 0.18 240);
--bp-ring-light: oklch(0.47 0.18 240 / 0.5);
--bp-primary-dark: oklch(0.74 0.12 240);
--bp-primary-hover-dark: oklch(0.8 0.12 240);
--bp-ring-dark: oklch(0.74 0.12 240 / 0.6);
}Other layout, surface, and typography values are --nb-* properties in styles/globals.css. Existing --sl-* aliases keep older Learn widgets styled during migration.
<Code> highlights with github-light / github-dark-default. Markdown code fences go through
@cloudflare/nimbus-docs's own pipeline, whose default dark theme (github-dark) puts comments at 3.95:1
on the dark card — under WCAG AA. Set the same themes in the site's astro.config.mjs so fences match
<Code> and pass; nimbus-docs keeps an explicitly set theme:
markdown: { shikiConfig: { themes: { light: 'github-light', dark: 'github-dark-default' }, defaultColor: false } },The right table of contents uses Cloudflare's orange active rail by default. Override --bp-toc-active on :root if a consuming site needs a different navigation accent.
Guide feedback and Edit page / Report issue links render below the right table of contents, or below the article when the rail is hidden. Feedback saves a Yes/No vote in localStorage under booleanpress:docs:feedback:v1:<canonical-page-url>. The client code is isolated in components/ui/feedback/feedback.client.ts for a future API integration; no vote is sent to a server.
