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

@tschk/moonshine-next

v0.4.49

Published

Next.js API surface reimplemented on moonshine — drop Next, keep the imports

Readme

@tschk/moonshine-next

Moonshine reimplements the Next.js API surface. It does not wrap, re-export, or depend on Next — next is not a dependency, a peer dependency, or a devDependency of this package. You keep writing next/link, next/navigation, next/image; moonshine provides them, on its own signal runtime and router.

import { createSignal, useSignal } from "@tschk/moonshine-next";
import { useRouter, usePathname } from "@tschk/moonshine-next/navigation";
import Link from "@tschk/moonshine-next/link";
import Image from "@tschk/moonshine-next/image";
import { headers, cookies } from "@tschk/moonshine-next/headers";
import { moonshineRoute, moonshineJson } from "@tschk/moonshine-next/server";

Peers: react >= 18, react-dom >= 18. That is the whole runtime requirement.

Drop-in: keep your existing imports

You do not have to rewrite a single import. Map the next/* specifiers onto this package at build time.

Bun

import { moonshineNextPlugin } from "@tschk/moonshine-next/aliases";

await Bun.build({
  entrypoints: ["./app/page.tsx"],
  plugins: [moonshineNextPlugin()],
});

For bun run / bun test, register it in a preload file:

// bun-preload.ts
import { plugin } from "bun";
import { moonshineNextPlugin } from "@tschk/moonshine-next/aliases";

plugin(moonshineNextPlugin());
# bunfig.toml
preload = ["./bun-preload.ts"]

TypeScript

So the type checker follows the same mapping the bundler applies:

{
  "compilerOptions": {
    "paths": {
      "next/link": ["@tschk/moonshine-next/link"],
      "next/navigation": ["@tschk/moonshine-next/navigation"],
      "next/image": ["@tschk/moonshine-next/image"],
      "next/script": ["@tschk/moonshine-next/script"],
      "next/headers": ["@tschk/moonshine-next/headers"],
      "next/dynamic": ["@tschk/moonshine-next/dynamic"],
      "next/font/google": ["@tschk/moonshine-next/font/google"],
    },
  },
}

tsconfigPaths() from @tschk/moonshine-next/aliases returns exactly that object, and nextAliases is the raw specifier map if you need to feed another bundler (esbuild alias, Vite resolve.alias, webpack resolve.alias).

Supported

| Subpath | Provides | | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | . / ./client | moonshine signals + resources ("use client") | | ./navigation | useRouter usePathname useSearchParams useParams redirect permanentRedirect notFound useSelectedLayoutSegment(s) + usePathnameSignal | | ./link | default Link — client navigation, hover/viewport prefetch | | ./image | default Image — src alt width height fill priority loading sizes | | ./script | default Script — strategy mapped to async/defer | | ./headers | headers() cookies() draftMode() (server only) | | ./dynamic | default dynamic() with loading, ssr: false, .preload() | | ./font/google | named family loaders returning { className, style, variable, href } | | ./aliases | next/* → moonshine specifier mapping + Bun plugin | | ./shaders | moonshine WebGL fragment helpers | | ./server | moonshineRoute / moonshineJson / moonshineHtml + page helpers |

Behavioural notes where moonshine deliberately differs from Next:

  • Link prefetch is a browser hint. It appends <link rel="prefetch"> on hover and on viewport entry. There is no RSC payload to warm, so this is not Next's prefetch cache — it only primes the HTTP cache.
  • Link scroll follows the destination. Next always jumps to the top; moonshine only scrolls when the pathname or hash actually changes, so query-only navigation (filters, pagination, tabs) keeps your scroll position. Pass scroll explicitly to force either behaviour.
  • router.refresh() reloads the browser document. This requests fresh server output and resets local component state. State-preserving RSC refresh is unsupported. It is safe to call during SSR, where it does nothing.
  • router.prefetch() is a no-op. Use Link for browser prefetch hints.
  • Browser navigation stays with the browser. Links preserve external URLs, non-self targets, fragments and downloads without route prefetch. External router.push uses document assignment; external replace replaces the history entry. Same-origin absolute URLs retain SPA navigation. The existing safe-href policy prevents executable schemes from reaching document navigation.
  • redirect() / notFound() throw RedirectError / NotFoundError, exported from ./navigation so your renderer can catch them.
  • useSearchParams() reads the live query string. The moonshine router stores only the pathname, so the query is recovered from window.location.

Not supported

Be aware of these before migrating — none of them are stubbed to silently "work":

  • No image optimizer. next/image renders a plain <img>. src is served exactly as authored; quality and unoptimized are accepted and ignored. There is no resizing, no format negotiation, no srcSet generation, no loader / remotePatterns config, no blur placeholder.
  • No React Server Components. No "use server", no Server Actions, no server-only component tree, no streaming RSC payload.
  • No ISR / on-demand revalidation. No revalidate, no revalidatePath, no revalidateTag, no unstable_cache, no fetch cache semantics.
  • No middleware. middleware.ts is not read; there is no edge interception layer. Use the moonshine server directly.
  • No next/font self-hosting. next/font/google does not download or subset fonts at build time. It returns Next's { className, style, variable } shape plus an href, and you emit the Google Fonts <link> tags yourself via fontLinks(). That means a third-party request at runtime, which is exactly what Next's self-hosting avoids. next/font/local is not implemented.
  • No file-system routing conventions. layout.tsx, loading.tsx, error.tsx, template.tsx, route groups, parallel and intercepting routes are not interpreted. Use the moonshine router.
  • Bounded cookie mutation. cookies().set() and .delete() use the opt-in request response-header scope on Bun/Node. headers() has no response mutation API, and draftMode() always reports disabled. Streaming Server Actions and Next's render-phase restrictions are not implemented.
  • Other next/* modules are absent, including next/server (NextRequest / NextResponse), next/og, next/cache, and next/navigation's unstable_* APIs. Route Handlers take and return plain Request / Response; see ./server.

Does not export MoonshineRouter from the client entry — import it from @tschk/moonshine/router if you want the moonshine router directly.

See Moonshine README.

Request helpers require requestContext: true in Moonshine config (enabled by Next adoption). Bun/Node deployment and preview inject the runner from the same compiled bundle as the route modules; a separately imported context cannot scope an already-bundled route. Edge request context is currently unsupported.

cookies() retains synchronous reads (await cookies() also works). With requestContext: true, loaders, actions and buffered rendering can call set(name, value, options) or set({name, value, ...options}). Values are percent-encoded; invalid names and attributes reject before changing headers or same-request reads. The default path is /. Each write appends a separate Set-Cookie; later writes to the same name update the shared request-local jar. Ordinary incoming cookies still read without a response scope; mutation requires one. This does not install a Next runtime.

delete(name) expires the root-path cookie. Use delete({name, path, domain}) for another scope; the incoming Cookie header cannot reveal a cookie's original path or domain. Deletion leaves an empty tombstone in same-request reads; the browser applies expiration on receipt. Signed/encrypted sessions, domain ownership checks, cookie prefix policies, streaming and Next cache invalidation are outside this API. Standard expiry, age, security, SameSite, priority and partitioned attributes are serialized; browser acceptance still follows browser policy.