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

@koehler8/cms

v1.3.0

Published

Vertex CMS — lightweight, config-driven Vue 3 framework with theming, extensions, and SSG support

Readme

Vertex CMS

License: MIT npm

Vertex CMS is a lightweight, config-driven Vue 3 framework with theming, extensions, and SSG support.

Accessibility

Vertex CMS targets WCAG 2.2 Level AA as a hard requirement for the bundled components, the base theme, and the page wrapper. Sites that ship on this framework with the bundled pieces inherit a skip link, single <main> landmark, real form labels with aria-live errors, visible focus rings, modal focus trap+restore, AA-verified palette contrast, and prefers-reduced-motion support out of the box.

When extending or theming, don't regress AA. See the "Accessibility" section of CLAUDE.md for the requirements, the token naming conventions for safe colors, and the audit checklist to run when changing palette/markup/CSS.

Quick Start

1. Create a site repo

my-site/
  site/
    content/
      content.config.json    # { "baseLocale": "en" }
      en/
        site.json            # Site metadata and settings
        shared.json          # Shared content blocks
        pages/
          home.json          # Per-page content
    assets/img/              # Images and media
    components/              # Site-local Vue components (optional)
    style.css                # Site-specific overrides (optional)
  .env
  package.json
  vite.config.js

Or scaffold all of it in one step:

npx --package @koehler8/cms@^1.0.0 cms-create-site my-site

2. Install

Vertex CMS is published to the public npm registry (registry.npmjs.org) — no scope configuration needed:

npm install @koehler8/cms@^1.0.0 vue vue-router vite vite-ssg @vitejs/plugin-vue

3. Configure Vite

// vite.config.js
import cmsPlugin from '@koehler8/cms/vite';

export default {
  plugins: [
    cmsPlugin({
      siteDir: './site',
      themes: ['@koehler8/cms-theme-neon'],
      extensions: ['@koehler8/cms-ext-compliance'],
    }),
  ],
};

4. Run

npm run dev        # Development server
npm run build      # Production build (SPA)
npx cms-ssg-build  # Static site generation — memory-bounded shards + blank-page gate

(vite-ssg build also works directly, but cms-ssg-build wraps it with a bounded heap, route sharding for large sites, and a post-render check that fails the build if any page comes out blank.)

Plugin Options

| Option | Default | Description | |--------|---------|-------------| | siteDir | './site' | Path to site content directory | | frameworkRoot | (auto) | Path to @koehler8/cms package root | | locales | all 15 supported | Array of locale codes to enable | | themes | [] | Theme package names to register | | extensions | [] | Extension package names to register | | initialStateTrims | [] | Module paths exporting a hydration-payload trim hook (see below) |

Hydration payload

Each prerendered page embeds the config it needs as window.__INITIAL_STATE__ so the first client render reproduces the prerendered DOM instead of blanking while the async loader runs. On a content-heavy site that blob can dominate page weight, so there are two levers.

site.trimInitialState (default: on)

Embeds only the current page's entry from config.pages rather than every page's, and marks the result pagesPartial. After hydration the full config is fetched once in the background — deferred to requestIdleCallback, since the current page has already rendered identically and the full config only matters once the reader navigates.

Set it to false to embed the whole config. That flag is the single, complete rollback for everything in this section.

initialStateTrims

trimConfigToPage handles config.pages. It cannot touch config.shared, because knowing which shared content a route needs requires the site's own notion of what that route resolves to. A site closes that gap with a hook:

// vite.config.js
cms({ initialStateTrims: ['./site/components/payloadTrim.js'] })
// site/components/payloadTrim.js
export default function payloadTrim(config, { routePath, locale, pageId }) {
  if (!needsTrimming(config)) return config;   // same reference === no-op
  return { ...config, shared: prune(config.shared, routePath) };
}

The hook must be synchronous and pure — it runs once per prerendered route, which is thousands of times per build. Return the config unchanged (the same reference) to no-op; anything that is not a plain object is ignored.

The framework stamps sharedPartial: true when a hook actually changes something, so a hook cannot forget the marker and ship a payload that is permanently short with nothing to repair it. That marker triggers the same post-hydration reload as pagesPartial, so anything pruned is restored before it can be navigated to.

Never drop site, shared or pages wholesale. Framework components on pages your hook never reasons about read them directly (useIntroGate, FooterMinimal, ComingSoon, NotFound, Header), and a first client render without them is a hydration mismatch on every page's chrome. Returns that do so are discarded, as are hooks that throw.

Measured on a 767-page publication whose per-locale item bodies lived in shared.json: payload 65.42 MB → 11.01 MB (−83.2%), gzipped HTML 23.22 MB → 7.23 MB (−68.9%).

Site Configuration

Site content lives in site/content/, organized by locale:

site/content/
  content.config.json        # { "baseLocale": "en" }
  en/
    site.json                # Site metadata (flat dot-notation keys, sorted)
    shared.json              # Shared content (header, footer, socials)
    pages/
      home.json              # Per-page content
      privacy.json
  de/
    site.json                # German overrides (only translated keys)
    shared.json
    pages/
      home.json

All files use flat dot-notation keys in alphabetical order. The base locale (specified in content.config.json) is loaded first; other locale directories override only the keys they specify. Missing keys fall back to the base locale value.

Supported locales: en, fr, es, de, ja, ko, pt, ru, tr, vi, id, zh, th, hi, fil

Analytics & consent

Set "googleId" in site.json to enable Google Analytics. How the not-yet-answered consent state is treated is configurable:

{ "analytics.consentMode": "opt-in" }
  • "opt-out" (default) — analytics load while consent is pending and stop if the visitor declines. Common US posture.
  • "opt-in" — analytics stay off until the visitor explicitly accepts. Use this for sites with EU/EEA audiences (GDPR / ePrivacy).

Themes

Themes export a manifest with design tokens (palette, typography, surfaces, CTAs, etc.) that are applied as CSS variables at runtime.

  • Set the theme key in content/{baseLocale}/site.json to a theme slug
  • Omit it to use the built-in base theme
  • External themes are npm packages registered via the themes plugin option

See themes/base/theme.config.js for the full token structure.

Extensions

Extensions are npm packages that provide additional components, content defaults, and setup hooks.

Each extension has an extension.config.json manifest defining:

  • components -- Vue components with metadata (name, configKey, allowedPages, requiredContent)
  • entry / setup -- Optional initialization hooks
  • assets -- CSS and static file references
  • dependencies -- Required npm packages

The manifest JSON Schema ships with the package — point your editor or JSON-schema tooling at it:

{ "$schema": "node_modules/@koehler8/cms/extensions/manifest.schema.json" }

Built-in Components

| Component | Description | |-----------|-------------| | Header | Site header with navigation | | Footer / FooterMinimal | Full and minimal footer variants | | Hero | Hero banner section | | About / AboutValue | About and value proposition sections | | Contact | Contact form (Google Forms backend) | | Team | Team member grid | | Portfolio | Portfolio showcase | | Plan | Pricing/plan comparison | | Principles | Principles/values section | | Intro / IntroGate | Intro modal and gate | | ComingSoon / ComingSoonModal | Coming soon page and modal | | StickyCTA | Sticky call-to-action bar | | BackToTop | Scroll-to-top button | | Preloader | Page loading indicator | | Spacer15/30/40/60 | Vertical spacing utilities |

UI Components

  • SbCard -- Card component
  • SkeletonPulse -- Loading skeleton
  • UnitChip -- Unit/badge chip

Composables

| Composable | Description | |------------|-------------| | usePageConfig | Load and cache page configuration | | useComponentResolver | Resolve and validate component definitions | | usePageMeta | Apply head/meta tags via @unhead | | useEngagementTracking | Scroll depth and engagement analytics | | useIntroGate | Intro modal state management | | useComingSoonConfig | Coming-soon page configuration | | useLazyImage | Lazy image loading with IntersectionObserver | | usePromoBackgroundStyles | Promo section background styling |

Draft Mode

Pages can be gated behind a site-wide password before launch: "draft": true on a page, "draftPaths": [...] prefixes, or "draft": true site-wide in site.json, with the password in "draftPassword". The build replaces the plaintext password with a SHA-256 hash (plaintext never ships), gated pages render only the password gate in the SSG HTML, are marked noindex, and are omitted from sitemap.xml.

Know what the gate is — and isn't. Draft mode is a pre-launch convenience, not access control:

  • The password hash ships in the public bundle (unsalted SHA-256 — a weak or guessable password is trivially recoverable).
  • The gated page's content.* keys still ship in the page's hydration JSON so Vue can mount after unlock — a determined reader can parse them out of the HTML without the password.

For genuinely confidential content (NDA material, unannounced listings), don't put it in the page JSON until launch.

Testing

The framework uses Vitest with happy-dom for unit testing.

npm test                # Run all tests
npm run test:watch      # Watch mode
npm run test:coverage   # Coverage report

Tests live in tests/ mirroring the source structure (tests/utils/, tests/composables/, tests/themes/, etc.). See vitest.config.js for configuration.

CLI Commands

# Scaffold a new site / theme / extension
npx cms-create-site my-site
npx cms-create-theme my-theme
npx cms-create-extension my-extension

# Static site generation (memory-bounded shards + blank-page gate)
npx cms-ssg-build

# Generate favicon.ico, logo.png, og-image.jpg from source assets
npx cms-generate-public-assets --site-dir ./site

# Validate theme manifests (bundled + your site-local themes/)
npx cms-validate-themes --site-dir ./site

# Validate extension manifests (site-local extensions/ + named packages)
npx cms-validate-extensions --site-dir ./site @koehler8/cms-ext-compliance

(The Vite plugin also runs the same extension-manifest validation on every build and fails loudly on an invalid manifest — the CLIs are for checking outside a build.)

Exports

The supported public API surface — these specifiers are covered by semver:

// Build integration
import cmsPlugin from '@koehler8/cms/vite';            // Vite plugin
import { createCmsApp } from '@koehler8/cms/app';      // App factory

// Utils commonly used by site components and extensions
import { loadConfigData } from '@koehler8/cms/utils/loadConfig';
import { resolveAsset, resolveMedia } from '@koehler8/cms/utils/assetResolver';
import { useResponsiveImage } from '@koehler8/cms/utils/imageSources';
import { trackEvent } from '@koehler8/cms/utils/analytics';
import { hasAcceptedConsent } from '@koehler8/cms/utils/cookieConsent';
import { formatTokenAmount } from '@koehler8/cms/utils/formatNumber';
import { resolveThemeColor } from '@koehler8/cms/utils/themeColors';

// Bundled components and composables (by file)
import Header from '@koehler8/cms/components/Header.vue';
import { useComingSoonConfig } from '@koehler8/cms/composables/useComingSoonConfig';

The extension manifest schema is exported at @koehler8/cms/extensions/manifest.schema.json.

Other utils/*, composables/*, components/*, themes/*, and extensions/* deep imports resolve too, but anything not listed above is internal — it may move or change between minor versions.

Environment Variables

| Variable | Used By | Description | |----------|---------|-------------| | VITE_SHOW_COOKIE_BANNER | cookieConsent | Enable cookie consent banner | | VITE_APP_VERSION | appInfo | Override the framework version string reported in the bundle | | CMS_SITE_DIR | generate-public-assets | Site directory path (build scripts) | | FAVICON_BG / FAVICON_FG | generate-public-assets | Fallback favicon/og colors (default: stable per-title pair) |

License

MIT