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

@ominity/next

v1.7.0

Published

Next.js App Router integration layer for Ominity CMS.

Downloads

2,425

Readme

@ominity/next

Production-ready Next.js App Router integration layer for Ominity CMS.

@ominity/next is intentionally split into three concerns:

  • CMS integration: stable models + API client normalization around @ominity/api-typescript
  • Rendering engine: generic, recursive CMS component rendering with a project-owned component registry
  • Next helpers: route resolution, static params, metadata, sitemap, and draft mode utilities
  • Commerce/Auth utilities: API-first commerce client and SDK-backed OAuth2/auth helpers

This package does not include project UI components. Each consuming website owns its own React components and visual design.

Why this package exists

CMS-driven websites often need the same foundation repeatedly:

  • fetch CMS pages/routes/menus/locales
  • resolve translated slugs and locale-aware URLs
  • render deeply nested CMS component trees
  • keep pages server-first while allowing interactive client blocks
  • support SSG, ISR, and SSR without rewriting integration logic per project

@ominity/next provides that foundation with explicit APIs and small, testable modules.

Install

pnpm add @ominity/next @ominity/api-typescript@^1.1.6

If you use forms rendering, also install:

pnpm add react-hook-form

Peer dependencies:

  • next ^15 || ^16
  • react ^18 || ^19
  • react-dom ^18 || ^19

Quick start

1) Create a CMS client

import { createCmsClient } from "@ominity/next/cms";

export const cmsClient = createCmsClient({
  sdk: {
    serverURL: process.env.OMINITY_API_URL ?? "",
    security: {
      apiKey: process.env.OMINITY_API_KEY ?? "",
    },
    language: "en",
    channelId: process.env.OMINITY_CHANNEL_ID,
  },
  debug: {
    enabled: process.env.NODE_ENV !== "production",
  },
});

getLocales() now resolves languages through /localization/languages and merges channel defaults (/channels/current) when available.

2) Define your project registry

import { createCmsRegistry, defineCmsComponent } from "@ominity/next/cms/rendering";

import { HeroBlock } from "@/components/cms/hero-block";
import { CarouselBlock } from "@/components/cms/carousel-block"; // can be a Client Component

export const cmsRegistry = createCmsRegistry([
  defineCmsComponent("hero", HeroBlock),
  defineCmsComponent("carousel", CarouselBlock),
]);

3) Resolve route + render page in App Router

import { createRoutingConfig } from "@ominity/next/cms";
import { fetchCmsPageForParams } from "@ominity/next/next";
import { renderCmsPage } from "@ominity/next/cms/rendering";

import { cmsClient } from "@/lib/cms-client";
import { cmsRegistry } from "@/lib/cms-registry";

const routing = createRoutingConfig({
  defaultLocale: "en",
  locales: [
    { code: "en", language: "en", default: true },
    { code: "nl", language: "nl" },
  ],
  localeSegmentStrategy: "language",
  canonicalRedirectPolicy: "if-not-canonical",
});

export default async function CmsCatchAllPage({ params }: { params: { slug?: string[] } }) {
  const routes = await cmsClient.getRoutes();
  const resolved = await fetchCmsPageForParams({
    client: cmsClient,
    routes,
    params,
    routing,
  });

  if (!resolved) {
    return null;
  }

  if (resolved.route.shouldRedirect) {
    // optional: redirect(resolved.route.canonicalPath)
  }

  return renderCmsPage({
    page: resolved.page,
    registry: cmsRegistry,
    context: {
      page: resolved.page,
      locale: resolved.route.locale,
      path: resolved.route.incomingPath,
      preview: false,
      debug: false,
    },
  });
}

SSG / ISR / SSR

This package does not force one rendering mode.

  • Use generateCmsStaticParams for SSG path generation.
  • Use Next route-level revalidate for ISR.
  • Use dynamic rendering when SSR is required.

Client Components can be nested inside rendered CMS pages without making the whole route client-rendered.

Auth (server-side)

@ominity/next/auth now provides a robust server-first auth layer on top of @ominity/api-typescript@^1.1.6:

  • OAuth2 token issuance (password, refresh_token, and other supported grants)
  • user access token issuance (users/{id}/token)
  • MFA method flows (list/get/enable/disable/send/validate)
  • recovery code flows (list/regenerate/validate)
  • user OAuth account and customer lookups
  • password reset link + password reset helpers
  • signed auth session cookies (sealAuthSession / unsealAuthSession)

Example:

import { createAuthClient } from "@ominity/next/auth";

const auth = createAuthClient({
  sdk: {
    serverURL: process.env.OMINITY_API_URL ?? "",
    security: {
      apiKey: process.env.OMINITY_API_KEY ?? "",
    },
  },
});

const token = await auth.issuePasswordToken({
  username: "[email protected]",
  password: "secret",
  clientId: process.env.OMINITY_OAUTH_CLIENT_ID ?? "",
  clientSecret: process.env.OMINITY_OAUTH_CLIENT_SECRET ?? "",
});

Locale-aware links

createCmsLinkResolver accepts route objects or string links.

Built-in route defaults:

  • page/{locale?}/{slug}
  • product/{locale?}/p/{sku}-{slug} (prefix configurable)
  • category/{locale?}/c/{slug} (hierarchical slugs supported, prefix configurable)

Example route object:

{
  resource: "route",
  name: "page",
  locale: "en",
  parameters: {
    id: 2,
    slug: "contact-us"
  }
}

You can override link generation per route type with custom resolvers. You can also override the built-in product/category prefixes with defaultRoutePrefixes.

Localized slugs for hard-coded pages

For non-CMS routes (for example /auth/login), you can keep slug translations in JSON-style maps and let @ominity/next resolve canonical locale paths and alternates:

import {
  buildLocalizedSlugAlternates,
  buildLocalizedStaticPath,
} from "@ominity/next/next";

const slugByLocale = {
  en: "login",
  nl: "inloggen",
};

const canonicalPath = buildLocalizedStaticPath({
  routing,
  locale: "nl",
  prefixPath: "/auth",
  slugByLocale,
});
// -> /nl/auth/inloggen (depends on locale strategy)

const { alternates } = buildLocalizedSlugAlternates({
  routing,
  locale: "nl",
  prefixPath: "/auth",
  slugByLocale,
  baseUrl: "https://www.example.com",
});

These helpers use your configured/routing locales as the source of truth and gracefully fall back from locale code (e.g. nl-BE) to language code (e.g. nl) when needed.

buildLocalizedSlugAlternates is strategy-aware:

  • language: emits language-only hreflang keys (en, nl, ...)
  • country-language: emits country-language keys (en-BE, nl-BE, ...) and can expand all combinations with:
const { alternates } = buildLocalizedSlugAlternates({
  routing,
  locale: "nl-BE",
  slugByLocale,
  countries: ["BE", "NL"],
  languages: ["en", "nl"],
});

For CMS pages, buildNextMetadataFromPage accepts the same routing-aware inputs via:

  • routing
  • alternateLanguages
  • alternateCountries

Localized route templates

For routes that need nested static paths or placeholders, use localized templates instead of simple slug maps.

Examples:

  • payment: "checkout/payment" or "something/checkout/payment"
  • product: "p/{sku}-{slug}"
  • category: "c/{slug}"
import {
  buildLocalizedRouteAlternates,
  buildLocalizedRoutePath,
} from "@ominity/next/next";

const paymentPath = buildLocalizedRoutePath({
  routing,
  locale: "nl",
  templateByLocale: {
    en: "checkout/payment",
    nl: "afrekenen/betalen",
  },
});
// -> /nl/afrekenen/betalen (depends on locale strategy)

const productPath = buildLocalizedRoutePath({
  routing,
  locale: "nl",
  templateByLocale: {
    en: "p/{sku}-{slug}",
    nl: "product/{sku}-{slug}",
  },
  params: {
    sku: "ABC-123",
    slug: "fiets-band",
  },
});
// -> /nl/product/ABC-123-fiets-band

const { alternates } = buildLocalizedRouteAlternates({
  routing,
  locale: "nl",
  templateByLocale: {
    en: "checkout/payment",
    nl: "afrekenen/betalen",
  },
  baseUrl: "https://www.example.com",
});

Template notes:

  • {param} supports strings, numbers, and arrays (arrays expand as hierarchical segments when the segment is exactly {param}).
  • Mixed segments like {sku}-{slug} require scalar values.

next-intl bridge

@ominity/next now exposes next-intl APIs through @ominity/next/intl:

import {
  NextIntlClientProvider,
  defineRouting,
  useTranslations,
} from "@ominity/next/intl";

This lets starter projects consume next-intl via @ominity/next without adding a separate direct dependency first.

Forms module (new)

@ominity/next/forms provides the lounge-depot forms builder capabilities as a reusable package module:

  • FormRenderer client component for Ominity form definitions
  • createOminityFormSubmitHandler server route helper
  • route factories for submit/upload/update endpoints
  • built-in themes (tailwindDefaultTheme, unstyledTheme, loungeDepotFormTheme)
  • createFormsClient with response normalization + optional adapter integration
  • createShadcnFormAdapters / createShadcnFormComponents helper for shadcn UI wiring

Example:

"use client";

import { FormRenderer, tailwindDefaultTheme } from "@ominity/next/forms";

export function ContactForm({ form }: { form: unknown }) {
  return (
    <FormRenderer
      form={form}
      styled
      themeOverride={tailwindDefaultTheme}
      defaultPhoneCountry="BE"
    />
  );
}
import { createOminityFormSubmitHandler } from "@ominity/next/forms";

const handler = createOminityFormSubmitHandler({
  ominityApiKey: process.env.OMINITY_API_KEY ?? "",
  ominityBaseUrl: process.env.OMINITY_API_URL,
});

export const POST = (request: Request) => handler(request);

For lower-code App Router wiring, the package also exposes route factories:

import { createOminityFormSubmitRouteHandler } from "@ominity/next/forms";

export const POST = createOminityFormSubmitRouteHandler({
  ominityApiKey: process.env.OMINITY_API_KEY,
  ominityBaseUrl: process.env.OMINITY_API_URL,
  useMockData: process.env.OMINITY_USE_MOCK_DATA === "true",
});

Visitor tracking

Use a first-party Next route plus TrackingProvider so carts, orders, and browser tracking share one UUID visitorId.

Create a same-origin proxy route. A short neutral path such as /api/omt is less likely to be filtered than direct third-party analytics requests:

import { createOminityTrackingProxyRouteHandlers } from "@ominity/next/tracking/proxy";

export const { GET, POST } = createOminityTrackingProxyRouteHandlers({
  ominityApiKey: process.env.OMINITY_API_KEY,
  ominityBaseUrl: process.env.OMINITY_API_URL,
  enabled: process.env.OMINITY_TRACKING_ENABLED !== "false",
  debug: process.env.OMINITY_DEBUG_LOGS === "true",
});

Wrap your app with the client provider:

import { Ominity } from "@ominity/api-typescript";
import { createCommerceClient } from "@ominity/next/commerce";
import { ensureVisitorIdCookie } from "@ominity/next/tracking";
import { TrackingProvider } from "@ominity/next/tracking/provider";

const visitorId = ensureVisitorIdCookie(cookies());

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <TrackingProvider endpoint="/api/omt">
      {children}
    </TrackingProvider>
  );
}

const commerce = createCommerceClient({
  sdk: {
    serverURL: process.env.OMINITY_API_URL ?? "",
    security: { apiKey: process.env.OMINITY_API_KEY ?? "" },
  },
  visitorIdResolver: () => visitorId,
});

await commerce.createCart({ data: { currency: "EUR" } }); // visitorId is auto-injected

const sdk = new Ominity({
  serverURL: process.env.OMINITY_API_URL ?? "",
  security: { apiKey: process.env.OMINITY_API_KEY ?? "" },
});

await sdk.tracking.events.track({
  event: "page_view",
  url: "https://shop.example.com/products/desk-lamp",
  visitorId,
});

TrackingProvider automatically tracks:

  • page_view on initial render and App Router navigation
  • session_start once per browser tab/session
  • scroll_depth at 25/50/75/100
  • outbound_click for external links
  • file_download for download/file links
  • form_submit for native form submissions

The proxy forwards the original browser IP headers to Ominity, and the tracking controller can resolve them explicitly. This avoids storing the Next server IP when events are proxied server-to-server.

If a page originates from an Ominity resource such as a CMS page or product, register that page context once so every auto-tracked event on the page carries a stable backend relation:

import { TrackingPageMetadata } from "@ominity/next/tracking/provider";

<TrackingPageMetadata
  origin={page}
  originOptions={{
    locale: "en",
    path: "/about",
    canonicalPath: "/en/about",
    route: {
      resource: "route",
      name: "page",
      locale: "en",
      parameters: {
        id: page.id,
        slug: page.slug,
      },
    },
  }}
/>

This adds origin_resource metadata to the emitted event payloads.

It also supports opt-in custom click events via data-ominity-event:

<button
  data-ominity-event="button_click"
  data-ominity-title="Hero CTA"
  data-ominity-metadata='{"placement":"hero"}'
>
  Shop now
</button>

Public modules

  • @ominity/next – full surface
  • @ominity/next/cms – client, stable CMS types, routing, locales, metadata helpers
  • @ominity/next/cms/rendering – registry + recursive renderer
  • @ominity/next/next – App Router integration helpers
  • @ominity/next/forms – Ominity forms renderer + submit helpers
  • @ominity/next/commerce – SDK-backed commerce client + normalized cart/order/payment models
  • @ominity/next/auth – SDK-backed OAuth2, MFA, recovery code, password reset, and signed sessions
  • @ominity/next/tracking – visitor UUID cookie helpers for first-party tracking
  • @ominity/next/tracking/provider – auto-tracking client provider for App Router
  • @ominity/next/tracking/proxy – server-side first-party proxy helper

Documentation

  • docs/architecture.md
  • docs/rendering.md
  • docs/component-registry.md
  • docs/routing.md
  • docs/i18n.md
  • docs/ssg-isr-ssr.md
  • docs/examples.md
  • docs/forms.md
  • docs/auth.md
  • docs/troubleshooting.md

Development

pnpm typecheck
pnpm test
pnpm build