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

najm-next

v0.9.0

Published

Strict, versioned Next.js configuration for Najm applications — monorepo root discovery, build isolation, service-worker headers, and dev-origin policy with no per-app options

Readme

najm-next

Versioned Next.js configuration for Najm applications. One line per app, no options.

// next.config.ts
export { default } from 'najm-next/config';

That is the whole file. Najm owns the defaults; a bump of this package moves every app forward at once.

What the preset owns

| Concern | Behaviour | | --- | --- | | Monorepo root | Nearest ancestor that declares workspaces (or holds a lockfile), applied to both turbopack.root and outputFileTracingRoot. | | Build directory | NAJM_NEXT_DIST_DIR when set, otherwise .next. Absolute or escaping values are rejected. | | Service worker | Shared route and registration exports provide a privacy-safe offline fallback. Legacy public/sw.js or public/service-worker.js files still receive secure headers. | | Dev origins | Empty unless NAJM_NEXT_DEV_ORIGINS lists hosts. | | Server externals | reflect-metadata stays external so decorator metadata has one registry. | | Images | minimumCacheTTL of 31 days. | | Fingerprinting | poweredByHeader: false. | | Workspace imports | experimental.externalDir. | | Version | Throws below the supported Next floor, warns past the tested major. |

Deployment transport headers — HSTS, TLS policy, the rest of the edge posture — are not here. They belong to the reverse proxy / edge, which is the only layer that knows the deployment.

The request Content-Security-Policy, however, is app-owned: najm-next composes one nonce policy per document request in proxy.ts (see Request CSP and proxy composition below) and forwards the nonce into rendering. The edge must not emit a second enforcing Content-Security-Policy — two enforcing policies intersect (a resource must satisfy both), so an edge policy would silently block app/provider resources the request policy allows (validation ID CSP-04). If the edge needs visibility into violations, it may emit Content-Security-Policy-Report-Only, and only in coordination with the app policy.

Root discovery

Next walks to the outermost lockfile. A stray bun.lock in a home directory therefore becomes the tracing root of every app checked out beneath it. This package walks up from the app and stops at the first directory that declares workspaces, checking that before a lockfile at every level.

Dev origins

allowedDevOrigins gates dev-only assets and endpoints. The default is empty: nothing on the LAN reaches the dev server's HMR or source payloads until a host is named.

NAJM_NEXT_DEV_ORIGINS="127.0.0.1, 192.168.1.13"

Values are split on commas or whitespace, stripped of scheme and port, and deduped. *.example.dev is allowed; a bare * throws.

Escape hatch

For the rare app that genuinely diverges:

// next.config.ts
import { defineNajmNextConfig } from 'najm-next/configurable';

export default defineNajmNextConfig({
  serverExternalPackages: ['better-sqlite3', 'sharp'],
});

Overrides merge rather than replace. turbopack, images, and experimental merge key by key; serverExternalPackages and allowedDevOrigins concatenate and dedupe; headers() runs after the preset's rules so an app rule wins a conflict. Every other key replaces the preset's value outright.

PWA service worker

Najm owns the worker behavior and registration lifecycle. Each application only adds a route and mounts the client registration once:

// app/sw.js/route.ts
export { GET } from 'najm-next/pwa';
import { NajmPwaRegistration } from 'najm-next/pwa/react';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        {children}
        <NajmPwaRegistration />
      </body>
    </html>
  );
}

The default worker never caches application pages or API responses. Failed document navigations receive a small inline offline response. Registration is production-only and uses updateViaCache: 'none'.

An application may preserve a branded static offline document and its assets:

// app/sw.js/route.ts
import { createNajmServiceWorker } from 'najm-next/pwa';

export const GET = createNajmServiceWorker({
  cacheId: 'my-app',
  cacheVersion: 'v1',
  offlineUrl: '/offline.html',
  precache: ['/icons/app-192.png'],
});

Only those explicit paths are served from the cache. Cache cleanup is limited to the configured cacheId; it never deletes unrelated caches on the origin. For apps deployed below a base path, pass matching scriptUrl and scope props to NajmPwaRegistration.

Web push is opt-in and configured by the application. The generated worker accepts only bounded { notificationId, title, body } JSON, stores only the notification ID in notification data, and opens the configured same-origin inbox path:

export const GET = createNajmServiceWorker({
  push: {
    defaultTitle: 'Example App',
    notificationPath: '/inbox',
    icon: '/icons/app-192.png',
    badge: '/icons/app-192.png',
  },
});

The package supplies no product copy or destination. Invalid payloads and cross-origin configuration are ignored or rejected before notification UI is shown.

Runtime location configuration

Declare the standard Leaflet integration once with location: true. The Next server adapter adds its resolved config to the public snapshot and the proxy adds the matching CSP sources automatically:

export const app = defineNajmApp({
  id: 'my-app',
  appName: 'My app',
  currency: 'MAD',
  // auth, preferences and csp omitted here
  location: true,
});

The default is Leaflet centered at 33.5731, -7.5898, zoom 12, with the public OpenStreetMap tile endpoint and attribution. Its environment prefix is derived deterministically (my-app -> MY_APP_LOCATION). Omit location to mount no location context or network policy. A custom object can select Google, override the preset, or provide an explicit prefix. The lower-level defineNajmLocationRuntime API remains available for unusual integrations.

const location = defineNajmAppLocationRuntime(app)?.resolve(process.env);

The prefix creates <PREFIX>_MAP_PROVIDER, _DEFAULT_LATITUDE, _DEFAULT_LONGITUDE, _DEFAULT_ZOOM, _TILE_URL, and _TILE_ATTRIBUTION. Unknown providers and invalid production URLs resolve to disabled. Loopback HTTP tile URLs are accepted only when isDevelopment is explicitly true.

Google is an additive runtime option. The browser key is intentionally public and must be restricted by referrer and API in Google Cloud; no environment object is serialized:

export const schoolApp = defineNajmApp({
  // auth, preferences and csp omitted here
  location: {
    environmentPrefix: 'SCHOOL_LOCATION',
    allowedProviders: ['google'],
    defaults: {
    provider: 'google',
    center: { latitude: 33.5731, longitude: -7.5898 },
    google: {
      language: 'fr',
      region: 'MA',
      apiKeyEnvironmentFallbacks: ['NEXT_PUBLIC_GOOGLE_MAPS_API_KEY'],
    },
    },
  },
});

The primary variable is SCHOOL_LOCATION_GOOGLE_API_KEY; the explicit fallback above supports a documented transition from an existing public key name. Optional _GOOGLE_MAP_ID, _GOOGLE_LANGUAGE, and _GOOGLE_REGION variables are validated before entering the public runtime snapshot. Missing or malformed values disable the map with sanitized issue codes. The resolution also contributes the exact Google image, connection, script, and font sources to the request CSP.

Shared-safe app definition

najm-next/app holds one application's policy as pure, serializable data — routes, session mode, preference cookies, CSP extras, and the location environment prefix. It imports nothing (no React, Next, environment, auth, or theme), so config-only consumers load it without any optional dependency:

// src/najm.config.ts
import { defineNajmApp } from 'najm-next/app';

export const app = defineNajmApp({
  id: 'my-app',
  auth: {
    apiBaseURL: '/api',
    authPrefix: '/auth',
    publicRoutes: ['/', '/login'],
    protectedRoutes: ['/dashboard'],
    loginRoute: '/login',
    forbiddenRoute: '/forbidden',
    proxySessionMode: 'optimistic',
    rememberCookieName: 'my-app.remember',
  },
  preferences: {
    cookieNames: {
      language: 'my-app-ui-language',
      theme: 'my-app-ui-theme',
      timeZone: 'my-app-ui-timezone',
    },
    defaultTimeZone: 'Africa/Casablanca',
  },
  csp: {
    reportPath: '/api/csp-report',
    extraImgSrc: ['https://tile.openstreetmap.org'],
    frameSrc: ["'none'"],
  },
  theme: true,
  branding: true,
  location: true,
});

The definition is validated eagerly and frozen. proxySessionMode is structurally compatible with najm-auth's ProxySessionMode without importing it, so this entrypoint never pulls Auth into the proxy graph.

Server bootstrap

Applications using Najm Auth, Kit, and Theme can use the Next-specific adapter to avoid repeating request-reader, session, theme, and preference wiring:

// src/najm.server.ts
import 'server-only';
import { createNajmNextServerApp } from 'najm-next/app/next';

export const najmServer = createNajmNextServerApp({
  app,
  auth,
  theme: appTheme,
  themeOptions: {
    getServer: async () => (await import('@app/server')).server,
    basePath: '/api',
  },
  preferences,
  readSettings: readPublicUiSettings,
  fallbackSettings: { enabled: false },
});

export const { getSession, requireSession, requireRole, loadUiSnapshot } = najmServer;

It uses cookie, user, Accept-Language, and configured fallback preference sources by default. preferenceSources adds institution values or custom source orders without replacing Kit validation. Set acceptLanguage: false when an application's policy excludes that source. mapPreferences adds typed, render-only values such as text direction or a regional formatting locale after the stored values are normalized. The app definition and lower-level najm-next/app/server entry remain free of optional Auth, Kit, and Theme dependencies.

najm-next/app/server composes the existing request-scoped Auth, Theme, and UI owners through structural callbacks. Create it once at module scope. It starts independent session, cookie/header, appearance, branding, and public-setting reads concurrently; preference resolution waits only for its inputs. The returned session accessors remain lightweight and do not trigger UI/settings loading:

// src/najm.server.ts
import 'server-only';
import { cookies, headers } from 'next/headers';
import { createNajmServerApp } from 'najm-next/app/server';

export const najmServer = createNajmServerApp({
  app,
  auth: serverAuth,
  theme: serverTheme,
  readSettings: readPublicUiSettings,
  fallbackSettings: { enabled: false },
  readCookies: cookies,
  readHeaders: headers,
  resolvePreferences: ({ cookies, headers, session }) =>
    preferences.resolveOrdered(cookies, {
      user: session?.user as { language?: unknown },
      acceptLanguage: headers.get('accept-language'),
    }),
});

Settings fallback is for typed public display projections only. Operational session failures still propagate according to the Auth owner, and diagnostics are bounded summaries rather than raw thrown values. React cache() scopes settings and the full snapshot to one server render; the next request retries.

Client provider composition

Full Najm applications use the direct NajmAppProvider from najm-next/app/client. It owns Auth, one per-mount TanStack Query client, Kit UI and Theme branding; the complete public server snapshot is passed once:

'use client';
import { NajmAppProvider } from 'najm-next/app/client';

export function Providers({ snapshot, children }) {
  return (
    <NajmAppProvider
      authClient={auth.client}
      snapshot={snapshot}
      i18n={appI18n}
    >
      {children}
    </NajmAppProvider>
  );
}

The server projects only the allowlisted appName and currency display defaults into snapshot.app; the provider consumes those values and snapshot.settings.locationConfig automatically. Explicit display props remain available for legacy or advanced overrides. Pass a custom query integration only when overriding the defaults, and pass extensions directly. Extensions support beforeUi (inside Auth and Query) and insideUi (inside Kit UI and Theme branding) placements. The direct full provider always mounts Query; query={false} and boolean activation are unsupported. The low-level composer remains the no-Query entrypoint, while the deprecated factory retains its compatibility option. Query mode, Query constructor/provider, and the Auth client are mount-lifetime choices, so changing one requires an intentional provider remount. Snapshot, location props and ordinary UI props remain reactive and do not remount descendants.

createNajmAppProvider remains as a deprecated compatibility wrapper for applications using its captured useUiProps hook. That hook still runs inside Auth and Query, and explicit JSX props still win over derived values. New applications should use the direct component and a stable extension/provider slot when they need reactive application-specific integration.

najm-next/app/react remains the low-level composition entrypoint for minimal apps and unusual provider stacks. It imports no optional owners; those apps bind only the providers they installed.

The built-in TanStack adapter defaults queries to a 60-second stale window, a 10-minute garbage-collection window, no focus refetch, and one retry for network/unknown or 5xx failures. Mutations are not retried. Applications pass only partial queries or mutations overrides; the adapter retains unspecified defaults. TanStack Query remains optional and is loaded only from this leaf entrypoint.

The standard composition is Auth -> Query -> beforeUi -> UI -> Branding -> insideUi -> Location -> children. Query retry functions and QueryClient construction stay in client code and are never serialized.

Request CSP and proxy composition

najm-next/security generates a fresh nonce per request, composes the single enforcing policy, and wraps the existing Najm Auth proxy without reconstructing its response — status, redirects, body, response headers, and every Set-Cookie value survive, because the composition mutates the returned response in place and only overwrites the CSP header (which also guarantees no accidental second enforcing policy):

// src/proxy.ts
import { composeNajmProxy } from 'najm-next/security';
import { app } from './najm.config';
import { auth } from './auth';

export default composeNajmProxy({
  auth,
  app,
});

// The matcher stays a static literal in the app: Next analyzes it at build
// time and dynamic values are ignored.
export const config = {
  matcher: [
    '/((?!api|_next/static|_next/image|favicon.ico|.*\\.(?:css|js|map|json|txt|xml|ico|png|jpg|jpeg|gif|webp|svg|woff|woff2|ttf|webmanifest)$).*)',
  ],
};

Nonce mode is explicit (mode: "nonce"): nonce rendering is dynamic per request, so pages behind the proxy must render dynamically (await connection() / force-dynamic). Never apply this preset to a static export. Production policies never contain 'unsafe-eval' or wildcards; development adds 'unsafe-eval' to script-src only, where React needs it (matching the installed Next 16 CSP guidance, whose development example likewise retains upgrade-insecure-requests). No ws:/wss: scheme is composed without runtime evidence; real HMR WebSocket/browser behavior in development remains a consumer acceptance item, not something this unit-checked policy proves. style-src 'self' 'unsafe-inline' is retained deliberately until a compatible tightening is separately proven. Provider origins (Leaflet tiles, Google hosts, fonts, frames) are app policy plus location contributions — see the Kafil-style and School-style coverage in test/cspPolicies.test.ts and the production fixture in integration/csp-proxy.

CSP violation reports

najm-next/security/reports is the bounded /api/csp-report handler. It preserves the legacy and Reporting API batch envelopes, reads at most 8 KiB with early stream cancellation, and always answers 204 with an empty body — for valid, malformed, and oversized input alike. URLs are redacted to origins/keywords only (no path retention): absolute URLs reduce to their origin, data:/blob: payloads collapse to keywords, and relative or malformed values become redacted. A throwing sink is isolated per report and never fails the response:

// src/app/api/csp-report/route.ts
import { createCspReportHandler } from 'najm-next/security/reports';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

export const POST = createCspReportHandler();

The route sits beside the API catch-all deliberately and boots no backend.

Strict-CSP client initialization

najm-next/instrumentation/client opts Zod 4.4.x into JIT-less evaluation before it loads, so the new Function capability probe never fires under a strict CSP. It runs from the framework's pre-hydration hook — after the HTML document loads, before hydration and therefore before any form schema evaluates — so no inline <Script> is needed:

// src/instrumentation-client.ts
import { initNajmZodStrictCsp } from 'najm-next/instrumentation/client';

initNajmZodStrictCsp();

The module has no imports, touches no DOM, and stays inert when imported server-side.

Environment

| Variable | Default | Purpose | | --- | --- | --- | | NAJM_NEXT_DIST_DIR | .next | Build directory, for parallel builds such as E2E runs. | | NAJM_NEXT_DEV_ORIGINS | (empty) | Extra dev-server origins, comma or space separated. |

Notes

  • Requires patched Next releases: ^15.5.24 || ^16.3.6; tested through Next 16.
  • The app directory is process.cwd(), so run next from the app workspace (bun run --cwd apps/web build), not with a directory argument from the root.