npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nimbi-cms

v1.0.8

Published

Lightweight CMS client for static sites with Bulma UI and search/indexing features

Downloads

1,009

Readme


title: nimbiCMS author: Abel Vázquez Montoro date: 2026-03-16

nimbiCMS

Lightweight, lightspeed, SEO-first client-side Content Management System

The project

nimbiCMS is a client-side CMS for static sites that requires no database, no server build step, and no backend, just a bunch of Markdown or HTML pages and a minimalistic setup

Just drop your Markdown files into a folder, serve the site (GitHub Pages, S3, etc.), and the client will crawl the content, render Markdown to HTML, hook links, manage slugs and anchors, maintain navigation and search functionalities, and update SEO tags on the fly. All that while being compliant, fast and accesible!

lighthouse results

Editing content via the GitHub web editor works too—just save and refresh to see updates.

The code lives at https://github.com/AbelVM/nimbiCMS

Features

  • Client-side rendering of GitHub‑flavored Markdown via marked.js, no need for site compilation
  • No Jekyll, no Hugo, no CI at all
  • Perfect for static servers (GitHub Pages, S3, etc.)
  • FAST: this site, the site of the project, contains more than 1200 Markdown documents (almost 3000 references) and it's indexed in a blink!
  • Client-side search box built from headings and excerpts (enabled by default).
  • Reading time estimation
  • Code is organized into small modules to ease maintenance and testing.
  • Sticky per-page, dynamically generated TOC.
  • Bulma‑based UI components.
  • Runtime updates for SEO, Open Graph and Twitter meta tags.
  • Markdown headers management (yaml_metadata_block by pandoc)
  • Lazily loads images by default, while heuristically marking above‑the‑fold images as eager.
  • Image preview modal with zoom controls, wheel zoom, drag pan, double-click to zoom, and touch pinch support.
  • Syntax highlighting using highlight.js — languages are auto-registered when detected.
  • Simple theming (light/dark), Bulma and hightlight.js customization options.
  • Feeding support: RSS 2.0 at /?rss and ATOM 1.0 at /?atom
  • Dynamic sitemap at /?sitemap
  • Simplified deliverables: regardless all the dynamic imports, the bundle is kept in one JS file and one CSS file
  • Bundle is compact size
    • JS file: 243.74 kB, gzipped 70.62 kB (for UMD bundle)
    • CSS file: 504.87 kB, gzipped 47.85 kB (includes Bulma)
  • All the heavy work is managed by web workers to avoid hogging the main thread
  • Pluggable architecture through the available hooks
  • Fully typed and documented
  • MIT licensed

Runtime sitemap & feeds

nimbiCMS exposes client-side endpoints that generate sitemaps and feeds at runtime:

  • Endpoints: /?sitemap (also cosmetic #/?sitemap and path forms like /sitemap or /sitemap.xml) — returns an XML sitemap that uses canonical ?page= URLs.
  • /?rss and /?atom (also #/?rss, #/?atom, and path forms /rss, /rss.xml, /atom, /atom.xml) — return RSS 2.0 and Atom 1.0 feeds respectively.

Limitations & crawler advice

  • These endpoints are generated client-side; servers cannot set the HTTP Content-Type header for the generated XML. The implementation uses Blob/data URL fallbacks to present XML in browsers, but this is not guaranteed to satisfy all crawlers.
  • For reliable crawler indexing prefer a server-generated sitemap.xml (build-time) and list it in robots.txt. Use the runtime endpoints for development convenience or for crawlers that execute JavaScript.

Installation

From npm

npm install nimbi-cms
npm run build

From CDN

You can use jsDelivr or unpkg to load the bundles directly in the browser without installing.

<!-- jsDelivr ESM -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/nimbi-cms.css">
<script type="module">
  import initCMS from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/nimbi-cms.es.js'
  initCMS({ el: '#app' })
</script>

<!-- UMD (unpkg) -->
<script src="https://unpkg.com/[email protected]/dist/nimbi-cms.js"></script>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/nimbi-cms.css">
<script>
  // UMD exposes a global `nimbiCMS`
  nimbiCMS.initCMS({ el: '#app' })
</script>

For development with live reload:

npm run dev

How to use

Basic HTML example

<!doctype html>
<html><head><meta charset="utf-8">
<script src="/dist/nimbi-cms.js"></script>
<link rel="preload" href="/dist/nimbi-cms.css" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/dist/nimbi-cms.css"></noscript>
</head><body>
<div id="app" style="height:100vh"></div>
<script>
  nimbiCMS.initCMS({ el: '#app' })
</script>
</body></html>

Bundle formats

Examples showing how to consume each shipped bundle format produced by the build.

  • UMD (browser global)
<!-- include the UMD bundle and CSS -->
<link rel="stylesheet" href="/dist/nimbi-cms.css">
<script src="/dist/nimbi-cms.js"></script>
<div id="app"></div>
<script>
  // UMD exposes a global `nimbiCMS` object
  nimbiCMS.initCMS({ el: '#app' })
</script>
  • ESM (modern bundlers / <script type="module">)
<script type="module">
  import initCMS from '/dist/nimbi-cms.es.js'
  initCMS({ el: '#app' })
</script>
  • CJS (Node / CommonJS consumers)
const { initCMS } = require('./dist/nimbi-cms.cjs.js')
initCMS({ el: '#app' })

Notes:

  • The UMD build is a single, self-contained dist/nimbi-cms.js file that exposes the public API on the nimbiCMS global.
  • The ES build is dist/nimbi-cms.es.js and is ideal for modern bundlers and <script type="module"> usage.
  • The CJS build is dist/nimbi-cms.cjs.js for CommonJS consumers.
  • CSS is always shipped as dist/nimbi-cms.css and should be loaded alongside the script for styling. For performance optimization, it's advised to preload the CSS file like:
<link rel="preload" href="/dist/nimbi-cms.css" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/dist/nimbi-cms.css"></noscript>

Content Workflow

Drop .md and/or .html files into your content directory. No build step is necessary; a static server that serves the files is sufficient.

When loading .html files, only the body block is parsed. All the code in head is overriden, so, if you want some specific styling or script being run for that page, add them to body. Check assets/playground.html source for an example

Required files

  • _navigation.md — renders into the navbar; use Markdown links. Example nav markup:
[Home](home.md)
[Blog](blog.md)
[About](about.md)

-- home page — by default the library will use the first link found in _navigation.md when present. If no suitable link is found, the runtime will not automatically probe or fall back to _home.md; set the homePage option to explicitly select a page (for example index.html).

initCMS({ el: '#app', homePage: 'index.html' })
  • _404.md — optional fallback for 404 responses. When the server responds to a requested .md path with an HTML document (e.g., an SPA fallback serving index.html), the CMS treats that as a missing markdown page and will attempt to load /_404.md from the configured content base so a proper 404 page can be rendered instead of the site's index HTML.
initCMS({ el: '#app', notFoundPage: '_404.md' })

URL schemes — cosmetic vs canonical

nimbiCMS supports two coexisting URL schemas so that user-facing navigation can be friendly while SEO remains stable:

  • Cosmetic (user-facing): /#/slug[#anchor][?params] — preferred for visible navigation, TOC links, and search results. Example: https://example.com/#/blog/my-post#comments?lang=fr
  • Canonical (SEO / internal): /?page=slug[#anchor][&params] — used for canonical <link> tags, internal fetches, and sitemaps so indexing stays consistent. Example: https://example.com/?page=blog/my-post#comments&lang=fr

Behavior notes:

  • The UI prefers the cosmetic /#/slug form for links, but the library emits canonical URLs using the ?page= form (for example in <link rel="canonical">) to keep SEO and indexing stable.
  • Anchors and query parameters are preserved and placed in the correct order: the anchor follows the slug, and parameters are appended after the anchor.
  • Internal fetches (including worker-based requests) use canonical ?page= URLs so paths remain predictable on static hosts.
  • Because the cosmetic form relies on client-side routing, crawlers that do not execute JavaScript may require a server-side sitemap.xml or server-side routing for full indexing coverage.

This behavior is covered by the test-suite (see the canonical-link and URL handling tests).

Options

initCMS(InitOptions) mounts the CMS into a page. The table below summarizes the supported InitOptions. This options can be overrrided using URL parameters with the same name.

Core

| Option | Type | Default | Description | |---|---:|:---:|---| | el | string \ Element | required | CSS selector or DOM element used as the mount target. | | contentPath | string | /content | URL path to the content folder serving .md/.html files; normalized to a relative path with trailing slash. | | allowUrlPathOverrides | boolean | false | Opt-in: when true, contentPath, homePage, notFoundPage, and navigationPage may be overridden using URL params. | | debugLevel | 0 \|1 \| 2 \| 3 | 0 | Initial logging verbosity: 0=disabled, 1=errors, 2=errors+warnings, 3=all messages. |

Indexing and Search

| Option | Type | Default | Description | |---|---:|:---:|---| | searchIndex | boolean | true | Enable the runtime search index and render a search box. | | searchIndexMode | 'eager' | 'lazy' | 'eager' | When to build the index ('eager' on init, 'lazy' on first query). | | indexDepth | 1 \| 2 \| 3 | 1 | How deep headings are indexed (H1, H2, H3). | | noIndexing | string[] | — | Paths (relative) to exclude from discovery and indexing. | | skipRootReadme | boolean | false | When true, skip link discovery inside a repository-root README.md. | | manifest | Record<string,string> | null | Optional in-memory content manifest mapping absolute paths (for example /content/page.md) to raw page text. When provided the runtime pre-seeds allMarkdownPaths and slug mappings and avoids network discovery; this is commonly used by build-time tooling that inlines page sources into a bundle. The init routine also honors a global __NIMBI_CMS_MANIFEST__ if present. |

Routing and Pages

| Option | Type | Default | Description | |---|---:|:---:|---| | navigationPage | string | '_navigation.md' | Path for the navigation markdown used to build the navbar (.md or .html). | | homePage | string | first link of navigationPage | Path for the site home page (.md or .html). | | notFoundPage | string\|null | null | Path for the not-found page (.md or .html). When unset (null) the runtime renders a small inline "Not Found" message linking to the configured homePage instead of attempting to fetch '_404.md'. | | exposeSitemap | boolean | true | When true the client exposes a runtime sitemap at /sitemap.xml and /sitemap.html. The sitemap is generated at runtime (no build-time generation) and emits canonical ?page= URLs (for example /?page=blog/my-post) while the UI preserves cosmetic /#/slug navigation. Requires SPA fallback (the server must serve index.html for /sitemap* requests). Disable with initCMS({ exposeSitemap: false }). For broad crawler coverage prefer generating a server-side sitemap.xml and listing it in robots.txt. You can also get an XML sitemap at /?sitemap endpoint. |

Note: All these files must be within contentPath

Styling and Theming

| Option | Type | Default | Description | |---|---:|:---:|---| | defaultStyle | 'light' | 'dark' | 'system' | 'light' | Initial UI theme. | | bulmaCustomize | string | 'none' | 'none' (bundled), 'local' (load <contentPath>/bulma.css) or a Bulmaswatch theme name to load remotely. | | navbarLogo | string | 'favicon' | Small site logo placed at the leftmost position of the navbar. Supported values: none, favicon (uses PNG favicon when available), a path or URL to an image, copy-first (use first image from homePage), and move-first (use first image from homePage and remove it from the rendered page). |

Localization

| Option | Type | Default | Description | |---|---:|:---:|---| | lang | string | — | UI language code (e.g. en, de). | | l10nFile | string \| null | null | Path to a JSON localization file (relative paths resolve against the page). | | availableLanguages | string[] | — | When set, treats a leading path segment as a language code and maps slugs per-language. |

Caching and Performance

| Option | Type | Default | Description | |---|---:|:---:|---| | cacheTtlMinutes | number | 5 | TTL for slug-resolution cache entries (minutes). Set 0 to disable expiration. | | cacheMaxEntries | number | — | Maximum entries in the router resolution cache. | | crawlMaxQueue | number | 1000 | Upper bound on directories queued during breadth-first crawl (0 disables the guard). | | fetchConcurrency | number | 'auto' | 'auto' | Limits the number of simultaneous network fetches used for probing external HTML, building the search index, and slug resolution. Accepts a positive integer or 'auto' (default). When 'auto', nimbiCMS derives a safe concurrency from navigator.hardwareConcurrency and caps it at 5. Set to 1 to serialize requests. | | negativeFetchCacheTTL | number | 60000 | Milliseconds to keep negative (failed) fetch responses in cache to avoid repeated failing requests. Default 60000 (1 minute). Set to 0 to disable negative caching. |

Advanced and Extensions

| Option | Type | Default | Description | |---|---:|:---:|---| | markdownExtensions | Array<object> | — | marked-style extension/plugin objects registered at init via addMarkdownExtension(). | | markdownPaths | string[] | — | Optional host-provided list of markdown paths used by slug resolution/search. |

API

The nimbi-cms package exports a small set of helpers in addition to the default initCMS export. These are available as named imports in ESM and as properties on the nimbiCMS global in UMD builds.

For a complete listing of exported symbols and TypeScript types, see the documentation.

Note: the default export (initCMS) and the default alias are intentionally excluded from the list below.

Version

  • getVersion() — returns a Promise<string> that resolves to the shipped package version (e.g. "0.1.0"). Useful for displaying build metadata or detecting whether the loaded bundle matches a deployed backend.
import { getVersion } from 'nimbi-cms'
getVersion().then(v => console.log('nimbiCMS version', v))

Debugging

  • setDebugLevel(level) — adjust runtime logging verbosity programmatically. level is a number 0..3 where 0 disables messages, 1 enables errors, 2 enables errors and warnings, and 3 enables info/log messages. Example:
import { setDebugLevel } from 'nimbi-cms'
setDebugLevel(2)

Hooks (extension points)

These helpers let you hook into internal rendering and navigation without forking the source.

  • addHook(name, fn) — register a callback for one of the supported hook points ('onPageLoad' | 'onNavBuild' | 'transformHtml').
  • onPageLoad(fn) — fired after a page is rendered and inserted into the DOM. Useful for analytics, adding UI enhancements, or triggering client-side behavior once content is available.
  • onNavBuild(fn) — fired after the navigation HTML is constructed but before it is attached to the document; ideal for mutating nav links, injecting controls, or adding a search input.
  • transformHtml(fn) — fired just before an article node is appended; gives you access to the generated DOM element and HTML string so you can alter structure, add attributes, or instrument the output.
  • runHooks(name, ctx) — programmatically invoke hook callbacks (useful in tests or when you want to replay a lifecycle event after manually inserting content).
  • _clearHooks() — clear all registered hooks. This is mainly intended for unit tests so each test can start with a clean hook slate.
import { onPageLoad, onNavBuild, transformHtml } from 'nimbi-cms'

onPageLoad(({ pagePath, article }) => {
  console.log('page rendered', pagePath)
  // e.g. initialize custom widgets inside the loaded article
})

onNavBuild(({ navWrap }) => {
  const btn = document.createElement('button')
  btn.textContent = 'Toggle theme'
  btn.onclick = () => setStyle('dark')
  navWrap.querySelector('.navbar-end')?.appendChild(btn)
})

transformHtml((html, article) => {
  // Add a data attribute to every rendered page
  article.dataset.nimbiRendered = 'true'
})

Theming & Styling helpers

  • ensureBulma(bulmaCustomize?, pageDir?) — ensures Bulma is loaded. Pass 'local' to load a local bulma.css (looks in the page directory and /${pageDir}/bulma.css), or pass a Bulmaswatch theme name (e.g. 'flatly') to load from unpkg.
  • setStyle(style) — switch between light/dark/system modes by updating data-theme and the is-dark class on the document. This matches the behavior of the built-in theme toggle.
  • setThemeVars(vars) — apply a set of CSS custom properties (e.g. { "--primary": "#06c" }) on the document root for runtime theming without rebuilding.
import { ensureBulma, setStyle, setThemeVars } from 'nimbi-cms'

// Load a Bulmaswatch theme from unpkg
ensureBulma('flatly')

// Or load a local bulma.css (useful for self-hosted overrides)
ensureBulma('local', './content/')

setStyle('dark')
setThemeVars({ primary: '#06c', 'font-family': 'system-ui' })

Localization helpers

  • t(key, ...args) — translate a UI string key using the currently loaded locale dictionary. Supports parameter substitution like t('helloUser', 'Alice').
  • loadL10nFile(url) — fetch and merge a JSON localization file into the current dictionary. Does not automatically rerender UI (useful for on-demand language packs).
  • setLang(code) — switch the UI language at runtime (e.g. 'en', 'de'). This updates internal strings and affects slug resolution when availableLanguages is configured.
import { t, loadL10nFile, setLang } from 'nimbi-cms'

// Dynamic translation
console.log(t('navigation'))

// Load an external translations file
await loadL10nFile('/i18n/l10n.json')
setLang('de')

Code highlighting helpers

  • registerLanguage(name, modulePath) — dynamically register a highlight.js language. Useful to lazy-load rarely used languages from a CDN.
  • loadSupportedLanguages() — preload the supported languages map used by the on-demand language loader.
  • observeCodeBlocks(root) — scan a DOM subtree and apply syntax highlighting to code blocks using the currently registered languages.
  • setHighlightTheme(name, { useCdn }) — switch the theme used by highlight.js (optionally fetch the CSS from CDN when useCdn=true).
  • SUPPORTED_HLJS_MAP — map of supported highlight.js language identifiers (useful for building language selection UIs).
  • BAD_LANGUAGES — list of languages that should not be auto-registered (usually because they are unsupported or conflict with browser file types).
import { registerLanguage, observeCodeBlocks, setHighlightTheme } from 'nimbi-cms'

registerLanguage('r', 'https://unpkg.com/highlight.js/lib/languages/r.js')
observeCodeBlocks(document.body)
setHighlightTheme('monokai', { useCdn: true })

Examples

This very site is running nimbiCMS!

The files used to do so, apart from the bundle, are:

  • index.html
  • README.md (this file)
  • LICENSE.md
  • _navigation.md
  • _404.md
  • assets/playground.html

Theming and Customization

Bulma is bundled by default. To alter styles at runtime, use bulmaCustomize with 'local' or a Bulmaswatch theme name:

initCMS({ el: '#app', contentPath: './content', bulmaCustomize: 'flatly' })

For build‑time custom Bulma, replace the import in src/nimbi-cms.js with your compiled CSS and rebuild.

Light/dark toggling is managed via defaultStyle or setStyle(); highlight colors can be changed with setHighlightTheme() (CDN load if useCdn=true).

Localization

  • lang option forces a UI language (short code, e.g. en, de).
  • l10nFile may point to a JSON file of translations; relative paths resolve against the page directory.
  • Built‑in defaults live in DEFAULT_L10N; loaded files merge on top and fall back to English.

To localize content (e.g. content/en/ and content/fr/), point contentPath at the desired language directory (for example, contentPath: './content/en/'). The lang option only affects UI strings, not which content directory is used.

Example:

// initial render (English content + UI)
initCMS({ el: '#app', contentPath: './content/en/', lang: 'en', availableLanguages: ['en','fr'] })

// later (French content + UI). This typically requires re-initializing the
// CMS or reloading the page with the new configuration.
initCMS({ el: '#app', contentPath: './content/fr/', lang: 'fr', availableLanguages: ['en','fr'] })

After initialization you can also change only the UI language at any time by calling the exported setLang(code) helper. This updates internal state so subsequent calls to t() return strings from the new dictionary.

Note: setLang() does not reload or re-initialize the CMS; it only affects UI strings and the slug resolution logic when availableLanguages is configured. To switch content folders (e.g. content/en/content/fr/), re-run initCMS() with a different contentPath (and availableLanguages).

Example translation file:

{
  "de": { "navigation": "Navigation", "onThisPage": "Auf dieser Seite" }
}

Usage:

// contentPath is optional
initCMS({ el: '#app', l10nFile: '/i18n/l10n.json', lang: 'de' })

// later, switch to French at runtime
import { setLang } from 'nimbi-cms'
setLang('fr')

Runtime path sanitization

To reduce the risk of accidental exposure or path traversal on static hosts, the client sanitizes and normalizes runtime path options. Important behaviour changes:

  • contentPath, homePage, and notFoundPage are not accepted from the page URL query string by default. These values may be provided programmatically via the initCMS() options object only.
  • When the host page explicitly opts in by passing allowUrlPathOverrides: true to initCMS(), the library will consider URL query string overrides. Even in that mode the values are validated and unsafe values are rejected.

Sanitization rules applied client-side:

  • contentPath: must be a non-empty string, must not contain .. segments, must not be an absolute URL (no protocol://), must not start with //, and must not be an absolute filesystem path (leading / or Windows drive prefix). The value is normalized to a relative path with a trailing slash.
  • homePage / notFoundPage: must be a simple basename (no slashes), only contain letters, numbers, dot, underscore or hyphen, and must end with .md or .html. Example safe names: index.html, _home.md.

If an unsafe value is detected the library will throw a TypeError when initializing. Unit tests were added to cover the common misuse cases (tests/init.sanitization.test.js) and the existing URL-override tests ensure the default behaviour (ignoring URL-provided paths) remains safe.

If you need an advanced opt-in for integration tests or unusual hosting environments, use allowUrlPathOverrides: true with caution and only when you control the embedding page and the static host configuration.

Opt-in usage example (cautious)

If you really need URL-driven overrides (for example in an integration test or a special controlled embed scenario), you must enable them explicitly in script code — they cannot be enabled from the URL itself. Only do this when you control both the embedding page and the static host's content layout.

UMD example (bundle exposes nimbiCMS):

<script>
  // Only enable in trusted environments
  nimbiCMS.initCMS({ el: '#app', allowUrlPathOverrides: true })
</script>

ES module example:

import initCMS from 'nimbi-cms'

// Only enable in trusted environments where the host page is controlled
initCMS({ el: '#app', allowUrlPathOverrides: true })

Note: enabling allowUrlPathOverrides still runs client-side validation; if an unsafe value is supplied the call to initCMS() will throw a TypeError. Prefer passing contentPath, homePage, and notFoundPage directly in the options object from secure script code rather than relying on URL query parameters.

Runtime sitemap (dynamic)

When enabled (default true) nimbiCMS can expose a runtime-generated sitemap intended for crawlers and bots that execute JavaScript. The runtime sitemap is available at:

  • /sitemap.xml — XML sitemap using canonical ?page= URLs
  • /sitemap.html — optional simple HTML listing (not a full UI)

Key notes:

  • Runtime-only: the sitemap is built in the browser from discovered content paths; nothing is generated at build time.
  • Canonical URLs: the sitemap emits ?page=... canonical URLs (for SEO and indexing) while the SPA UI keeps cosmetic /#/slug links for user navigation.
  • SPA fallback required: your static host must serve index.html for requests to /sitemap.xml or /sitemap.html so the client-side handler can render the sitemap.
  • Disabled when needed: opt out by passing exposeSitemap: false to initCMS():
initCMS({ el: '#app', exposeSitemap: false })

For widest crawler compatibility (search engines that don't execute JavaScript reliably) prefer generating and serving a server-side sitemap.xml and listing it in robots.txt. The runtime sitemap is a convenience for JS-aware crawlers and for environments where generating a sitemap at build time is not possible.

Available Bulmaswatch themes

The list of available Bulma themes is

default, cerulean, cosmo, cyborg, darkly, flatly, journal, litera, lumen, lux, materia, minty, nuclear, pulse, sandstone, simplex, slate, solar, spacelab, superhero, united, yeti.

See previews at https://jenil.github.io/bulmaswatch/ and load via bulmaCustomize option or ensureBulma method.

Keep in mind that some themes do not play well with certain color schemas.

Using with GitHub Pages and the GitHub file editor

nimbiCMS works well with GitHub Pages and the built-in GitHub web file editor. Minimal steps:

  • Enable GitHub Pages for the repository (Settings → Pages) and choose the branch/folder you want to publish (e.g., gh-pages or main / /docs).
  • If your content includes underscore-prefixed files (for example _navigation.md or _home.md), GitHub's Jekyll processor will ignore them by default. Add an empty .nojekyll file at the repository root to disable Jekyll so those files are served
  • Ensure your published site serves the built dist assets (upload dist/ to the chosen branch or use a build step / GitHub Action to publish).
  • Place your content under a folder (default: content/) or set contentPath when calling initCMS() to point somewhere else.

Editing content via the GitHub web editor:

  1. Open the repository on GitHub and navigate to the content/ folder (or your chosen contentPath).
  2. Click any .md file, then click the pencil icon to edit the file in the browser.
  3. Make changes and commit them directly to the branch. The published site will receive the updates on the next Pages build (or immediately if you host the dist on the same branch).
  4. Refresh the site to see the updated content. nimbiCMS loads content at runtime, so browser refresh shows the latest files.

Tips:

  • Add or update content/_navigation.md to control the navigation bar; the nav is re-built when pages are crawled.
  • If you publish dist/ separately (for example to gh-pages), consider a GitHub Action to build and push dist/ automatically from main.

Security note: Avoid exposing sensitive paths via URL query options; do not allow untrusted runtime overrides for contentPath, homePage, or notFoundPage unless you validate them server- or build-side.

Troubleshooting

If you find a bug, have a feature request or a question, just open an issue

Content not loading / 404 pages

  • Verify contentPath is correct and matches the directory containing your .md files.
  • Ensure your static server is serving those files (a 404 is often because the content folder isn’t published or the path is wrong).

Styles not appearing / Bulma missing

  • Ensure dist/nimbi-cms.css is loaded alongside dist/nimbi-cms.js.
  • If using bulmaCustomize: 'local', confirm bulma.css exists in the content path or at /bulma.css.
  • If using a Bulmaswatch theme, verify the theme name is correct (see Available Bulmaswatch themes).

Scripts failing / no mount element

  • Make sure the mount element exists (<div id="app"></div>) and initCMS({ el: '#app' }) uses the correct selector.

I get a console error!

One of the features of nimbiCMS is folder crawling for enhanced indexing performance, so it's going to try and fetch your contentPath folder just in case your server response is an HTML listing the files in the folder. If your server has this functionality disabled, which is quite common, it will send back a 404 response that will trigger an error in the dev console of your browser, but no worries, it's an expected one and breaks nothing. Your console might looks like this when this happens

nimbi-cms.js:71  GET https://example.com/contentfolder/ 404 (Not Found)
nimbi-cms.js:71 [slugManager] crawlAllMarkdown: directory fetch non-ok {url: 'https://example.com/contentfolder/', status: 404}