@molecule/app-routing-next

Provider bond · routing · App (browser) · v1.0.2 · Apache-2.0

Next.js App Router provider for molecule.dev

npm install @molecule/app-routing-next

npm · Source on GitHub · Implements @molecule/app-routing

How it works

@molecule/app-routing-next is a provider bond on the app (browser) side: it implements the routing core interface (@molecule/app-routing) with a concrete vendor or library behind it.

Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.

'use client'

import { useParams, usePathname, useRouter, useSearchParams } from 'next/navigation'
import { useMoleculeRouter } from '@molecule/app-routing-next'

// Mount once near the root layout. useMoleculeRouter recreates + re-bonds on every
// route change (via setRouter in an effect) so location/params stay current and
// @molecule/app-routing's navigate() drives THIS App Router.
export function MoleculeRouterBridge({ children }: { children: React.ReactNode }) {
  useMoleculeRouter({
    navigation: useRouter(),
    pathname: usePathname(),
    searchParams: Object.fromEntries(useSearchParams()),
    params: useParams(),
  })

  return children
}

Works with: @molecule/app-i18n, @molecule/app-logger, @molecule/app-routing

Reference

Next.js App Router provider for molecule.dev.

Adapts Next.js App Router navigation to the molecule Router interface from @molecule/app-routing, so molecule packages and app code can navigate, read params/query, and register guards without importing next/navigation.

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

Quick Start

'use client'

import { useParams, usePathname, useRouter, useSearchParams } from 'next/navigation'
import { useMoleculeRouter } from '@molecule/app-routing-next'

// Mount once near the root layout. useMoleculeRouter recreates + re-bonds on every
// route change (via setRouter in an effect) so location/params stay current and
// @molecule/app-routing's navigate() drives THIS App Router.
export function MoleculeRouterBridge({ children }: { children: React.ReactNode }) {
  useMoleculeRouter({
    navigation: useRouter(),
    pathname: usePathname(),
    searchParams: Object.fromEntries(useSearchParams()),
    params: useParams(),
  })

  return children
}

Type

provider

Installation

npm install @molecule/app-routing-next @molecule/app-i18n @molecule/app-logger @molecule/app-routing react
npm install -D @types/react

API

Interfaces

NextNavigation

Next.js navigation types (minimal subset to avoid direct next dependency).

interface NextNavigation {
  push(href: string, options?: { scroll?: boolean }): void
  replace(href: string, options?: { scroll?: boolean }): void
  back(): void
  forward(): void
  refresh(): void
  prefetch?(href: string): void
}

NextParams

Next Params interface.

interface NextParams {
  [key: string]: string | string[]
}

NextSearchParams

Next Search Params interface.

interface NextSearchParams {
  [key: string]: string | string[] | undefined
}

NextRouterConfig

Configuration options for the Next.js router provider.

interface NextRouterConfig extends RouterConfig {
  /**
   * Next.js navigation object (from useRouter).
   */
  navigation?: NextNavigation

  /**
   * Current pathname (from usePathname).
   */
  pathname?: string

  /**
   * Current search params (from useSearchParams).
   */
  searchParams?: NextSearchParams

  /**
   * Current dynamic params (from useParams).
   */
  params?: NextParams

  /**
   * Route definitions.
   */
  routes?: RouteDefinition[]
}

MiddlewareGuardRule

Middleware guard rule configuration.

interface MiddlewareGuardRule {
  /**
   * Path pattern to match (supports * wildcards).
   */
  match: string

  /**
   * Check function - return true to allow, string to redirect.
   */
  check: (request: {
    url: string
    pathname: string
    cookies: { get: (name: string) => { value: string } | undefined }
    headers: { get: (name: string) => string | null }
  }) => boolean | string | Promise<boolean | string>
}

RouteLocation

Current URL decomposed into pathname, search string, hash, navigation state, and unique key.

interface RouteLocation {
  /**
   * Current pathname.
   */
  pathname: string
  /**
   * Query string (including leading ?).
   */
  search: string
  /**
   * Hash (including leading #).
   */
  hash: string
  /**
   * State data passed with navigation.
   */
  state?: unknown
  /**
   * Unique key for this location.
   */
  key?: string
}

RouteMatch

Result of matching a URL against a route pattern (path, params, query string).

interface RouteMatch<Params extends RouteParams = RouteParams> {
  /**
   * Route path pattern.
   */
  path: string
  /**
   * Matched URL pathname.
   */
  pathname: string
  /**
   * Route parameters.
   */
  params: Params
  /**
   * Whether this is an exact match.
   */
  isExact: boolean
}

Options for programmatic navigation (replace vs push, carry state, preserve query/hash).

interface NavigateOptions {
  /**
   * Replace current history entry instead of pushing.
   */
  replace?: boolean
  /**
   * State to pass with navigation.
   */
  state?: unknown
  /**
   * Preserve current query params.
   */
  preserveQuery?: boolean
  /**
   * Preserve current hash.
   */
  preserveHash?: boolean
}

RouteDefinition

Route configuration entry (path pattern, name, auth requirements, roles, children).

interface RouteDefinition {
  /**
   * Route path pattern.
   */
  path: string
  /**
   * Route name (for named routes).
   */
  name?: string
  /**
   * Whether the route requires exact matching.
   */
  exact?: boolean
  /**
   * Whether the route requires authentication.
   */
  requiresAuth?: boolean
  /**
   * Required roles/permissions.
   */
  roles?: string[]
  /**
   * Route metadata.
   */
  meta?: Record<string, unknown>
  /**
   * Child routes.
   */
  children?: RouteDefinition[]
}

Router

Client-side router providing navigation, guards, route matching, and history control.

All routing providers must implement this interface.

interface Router {
  /**
   * Returns the current route location (pathname, search, hash, state).
   */
  getLocation(): RouteLocation
  /**
   * Gets the current route params.
   */
  getParams<T extends RouteParams = RouteParams>(): T
  /**
   * Gets the current query params.
   */
  getQuery(): QueryParams
  /**
   * Gets a specific query parameter.
   */
  getQueryParam(key: string): string | undefined
  /**
   * Gets the current hash.
   */
  getHash(): string
  /**
   * Navigates to a path.
   */
  navigate(path: string, options?: NavigateOptions): void
  /**
   * Navigates to a named route.
   */
  navigateTo(
    name: string,
    params?: RouteParams,
    query?: QueryParams,
    options?: NavigateOptions,
  ): void
  /**
   * Goes back in history.
   */
  back(): void
  /**
   * Goes forward in history.
   */
  forward(): void
  /**
   * Goes to a specific point in history.
   */
  go(delta: number): void
  /**
   * Updates the current query params.
   */
  setQuery(params: QueryParams, options?: NavigateOptions): void
  /**
   * Updates a specific query parameter.
   */
  setQueryParam(key: string, value: string | undefined, options?: NavigateOptions): void
  /**
   * Updates the current hash.
   */
  setHash(hash: string, options?: NavigateOptions): void
  /**
   * Checks if a path matches the current location.
   *
   * @returns `true` if the path matches the current route.
   */
  isActive(path: string, exact?: boolean): boolean
  /**
   * Matches a path pattern against a pathname.
   */
  matchPath<Params extends RouteParams = RouteParams>(
    pattern: string,
    pathname: string,
  ): RouteMatch<Params> | null
  /**
   * Generates a URL from a named route.
   */
  generatePath(name: string, params?: RouteParams, query?: QueryParams): string
  /**
   * Subscribes to route changes.
   */
  subscribe(listener: RouteChangeListener): () => void
  /**
   * Adds a navigation guard.
   */
  addGuard(guard: NavigationGuard): () => void
  /**
   * Registers route definitions.
   */
  registerRoutes(routes: RouteDefinition[]): void
  /**
   * Gets all registered routes.
   */
  getRoutes(): RouteDefinition[]
  /**
   * Destroys the router.
   */
  destroy(): void
}

RouterConfig

Configuration options for creating a router instance.

interface RouterConfig {
  /**
   * Router mode.
   */
  mode?: 'history' | 'hash' | 'memory'
  /**
   * Base path.
   */
  basePath?: string
  /**
   * Initial routes.
   */
  routes?: RouteDefinition[]
}

Types

RouteParams

URL path parameter key-value map extracted from dynamic route segments (e.g. { id: '123' }).

type RouteParams = Record<string, string>

QueryParams

URL query string parameter map (single values or arrays for repeated keys).

type QueryParams = Record<string, string | string[] | undefined>

Navigation guard function invoked before each navigation. Return false to cancel, a string/path to redirect, or void to allow.

type NavigationGuard = (
  to: RouteLocation,
  from: RouteLocation | null,
) => GuardResult | Promise<GuardResult>
  • to — The target route location being navigated to.
  • from — The current route location being navigated away from, or null on initial load.

RouteChangeListener

Callback invoked on each route change with the new location and the navigation action that triggered it.

type RouteChangeListener = (location: RouteLocation, action: 'push' | 'replace' | 'pop') => void

Functions

useMoleculeRouter(config)

Builds the molecule Router from next/navigation hook values AND bonds it via @molecule/app-routing's setRouter, so navigate()/getRouter() drive the REAL App Router (not the core's auto-created fallback that does full-page reloads).

Call it in a 'use client' component near the root layout, passing the values from useRouter()/usePathname()/useSearchParams()/useParams(). It rebuilds and re-bonds on every route change so location/params stay current.

function useMoleculeRouter(config: NextRouterConfig): Router
  • config — The Next.js router config (navigation, pathname, searchParams, params, optional routes) assembled from next/navigation hooks.

Returns: The bonded molecule Router.

createMiddlewareGuard(rules)

Next.js middleware helper for route guards.

function createMiddlewareGuard(
  rules: MiddlewareGuardRule[],
): (request: {
  url: string
  nextUrl: { pathname: string }
  cookies: { get: (name: string) => { value: string } | undefined }
  headers: { get: (name: string) => string | null }
}) => Promise<
  { redirect: string; continue?: undefined } | { continue: boolean; redirect?: undefined }
>
  • rules — Array of guard rules, each with a match pattern (supports * wildcards) and an async check function.

Returns: An async middleware function that returns { redirect: string } or { continue: true }.

createNextRouter(config)

Creates a Next.js App Router adapter.

function createNextRouter(config?: NextRouterConfig): Router
  • config — Configuration with Next.js's navigation (from useRouter()), pathname, searchParams, params, and optional routes.

Returns: A molecule Router with navigation, guards, query/hash management, and route matching.

createLinkHref(pathname, query, hash)

Creates a Next.js link href with query params.

function createLinkHref(pathname: string, query?: QueryParams, hash?: string): string
  • pathname — The base path (e.g. '/products').
  • query — Optional query parameters to append as a search string.
  • hash — Optional hash fragment (with or without leading #).

Returns: The assembled href string with path, query, and hash.

dynamicPath(pattern)

Creates a reusable path builder for Next.js dynamic routes. Replaces [param] and [...catchAll] segments with provided values.

function dynamicPath(pattern: string): (params: Record<string, string | string[]>) => string
  • pattern — A Next.js route pattern with [param] or [...catchAll] segments.

Returns: A function that accepts params and returns the resolved path string.

parseCatchAllParams(param)

Normalizes a Next.js catch-all route parameter into a string array. Handles undefined (returns []), a single string, or an existing array.

function parseCatchAllParams(param: string | string[] | undefined): string[]
  • param — The catch-all param value from Next.js route params.

Returns: An array of path segments.

Constants

provider

Default Next.js router provider (basic, no hooks). For full functionality, use createNextRouter with useRouter/usePathname/useSearchParams/useParams.

const provider: Router

parseQuery

Parses a URL query string (e.g. ?foo=bar&baz=1) into a QueryParams object. Duplicate keys produce string arrays.

const parseQuery: (search: string) => QueryParams
  • search — The query string to parse (with or without leading ?).

Returns: A key-value map of query parameters.

stringifyQuery

Serializes a QueryParams object into a query string with leading ?. Returns an empty string if no parameters are present.

const stringifyQuery: (params: QueryParams) => string
  • params — The query parameters to serialize.

Returns: The query string (e.g. ?foo=bar&baz=1) or ''.

matchPath

Matches a route pattern (e.g. /users/:id) against a pathname. Extracts named parameters from the URL.

const matchPath: <Params extends RouteParams = RouteParams>(
  pattern: string,
  pathname: string,
  exact?: boolean,
) => RouteMatch<Params> | null
  • pattern — Route pattern with :param placeholders.
  • pathname — The actual URL pathname to match against.
  • exact — If true, requires a full match (no trailing segments).

Returns: A RouteMatch with extracted params, or null if no match.

generatePath

Generates a URL path from a route pattern by substituting named parameters.

const generatePath: (pattern: string, params?: RouteParams) => string
  • pattern — Route pattern with :param placeholders (e.g. /users/:id).
  • params — Parameter values to substitute into the pattern.

Returns: The generated path with parameters URL-encoded.

Core Interface

Implements @molecule/app-routing interface.

Bond Wiring

Setup function to register this provider with the core interface:

import { setRouter } from '@molecule/app-routing'
import { provider } from '@molecule/app-routing-next'

export function setupRoutingNext(): void {
  setRouter(provider)
}

Injection Notes

Requirements

Peer dependencies:

  • @molecule/app-i18n ^1.0.1
  • @molecule/app-logger ^1.0.1
  • @molecule/app-routing ^1.0.1
  • react ^18.0.0 || ^19.0.0

Runtime Dependencies

  • @molecule/app-i18n

  • @molecule/app-logger

  • @molecule/app-routing

  • react

  • Use useMoleculeRouter(...) to bond the router from a 'use client' component near the root layout (the adapter cannot run in Server Components). It calls @molecule/app-routing's setRouter in an effect, so molecule packages' navigate()/getRouter() drive the real App Router.

  • Do NOT wire the exported provider const in a Next app. It is a no-hooks fallback: its location is frozen at import time, getParams() is always empty, and navigate() falls back to window.location.href — a full page reload that bypasses the App Router.

  • Forgotten wiring never errors. @molecule/app-routing's getRouter() auto-creates a plain browser router when nothing is bonded, so molecule packages keep "working" with full-page reloads — check wiring first when SPA navigation degrades.

  • createMiddlewareGuard() (for middleware.ts) returns plain { redirect: string } | { continue: true } objects — map them to NextResponse.redirect(new URL(result.redirect, request.url)) / NextResponse.next() yourself; returning the guard result directly does nothing.

  • searchParams must be a plain object — spread the hook value with Object.fromEntries(useSearchParams()).