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

@palamedes/next-plugin

v1.25.0

Published

Next.js integration for Palamedes using OXC-based macro transformation

Downloads

7,127

Readme

@palamedes/next-plugin

npm version CI Sponsored by Sebastian Software License: MIT OR Apache-2.0

The recommended Palamedes entry point for Next.js applications.

@palamedes/next-plugin wires Palamedes into Next.js so message macros are compiled before they leak into runtime, .po files load as part of the build, and catalog problems show up while the app is still easy to fix.

Status

  • Recommended for Next.js applications using App Router and Palamedes macros
  • Supports .po imports and source-string-first catalog semantics
  • Reports missing translations and ICU compatibility diagnostics during builds
  • Requires Next.js 16 (peerDependencies: next ^16); the emitted top-level turbopack.rules conditions and outputFileTracingRoot need the Next 16 config surface
  • Uses Turbopack as the verified default path on Next.js 16.2
  • Graph-split client messages are verified under Turbopack and webpack
  • The shipped example proves server rendering, localized "use server" actions, hydration, and client navigation
  • Also supports webpack as an opt-out / fallback path
  • Not a full Next.js starter or scaffolding tool

Start Here

Use the full copy-paste setup guide:

Installation

pnpm add @palamedes/core @palamedes/react @palamedes/runtime @palamedes/next-plugin server-only
pnpm add -D @palamedes/cli @palamedes/config

Minimal Setup

const { withPalamedes } = require("@palamedes/next-plugin");

module.exports = withPalamedes({});
locales: [en, de]
source-locale: en
catalogs:
  - path: src/locales/{locale}
    include: [src]

Transformed code expects getI18n() from @palamedes/runtime, so make sure the active i18n instance is available on both the client and the server before translated code executes.

For translated Client Components using PO catalogs, enable messageSplitting: true. Palamedes then owns the browser bootstrap: it loads only the document locale's fragments for Client Components and helpers that are actually present in the evaluated module graph. No application-owned catalog boundary, executable RSC payload, or inline script is required.

Catalog storage can be PO or FCL in palamedes.yaml, but the current Next loader is still a .po import loader. Keep direct app imports on .po unless a future adapter release explicitly documents .fcl imports.

For App Router Server Components on the Node runtime, use a server-only module with @palamedes/next-plugin/server. This follows the official RSC shape: keep server code behind server-only, memoize request work with React cache(), and bind direct macro calls to the complete Next render lifetime.

// src/lib/i18n.server.ts
import "server-only";

import { cache } from "react";
import { createNextServerI18nScope } from "@palamedes/next-plugin/server";
import type { PalamedesI18n } from "@palamedes/core";

export const serverI18n = createNextServerI18nScope<PalamedesI18n>();

const loadActiveServerI18n = cache(async () => {
  const locale = await resolveLocaleFromCookiesOrHeaders();
  const i18n = await loadI18n(locale);
  return { i18n, locale };
});

export async function createActiveServerI18n() {
  const active = await loadActiveServerI18n();
  serverI18n.activate(active.i18n);
  return active;
}
// app/page.tsx
import { t } from "@palamedes/core/macro";
import { createActiveServerI18n } from "@/lib/i18n.server";

function DownstreamServerTitle() {
  return <h1>{t`Welcome to Palamedes`}</h1>;
}

export default async function Page() {
  const { locale } = await createActiveServerI18n();
  return (
    <>
      <DownstreamServerTitle />
      <TranslatedClientContent locale={locale} />
    </>
  );
}

Enable the client graph bootstrap once in the Next configuration:

module.exports = withPalamedes(
  {},
  {
    messageSplitting: true,
  },
);

Each message-bearing browser module gets statically enumerable imports for its selected PO subset. It awaits only the import matching document.documentElement.lang, loads the fragment into a shared parser-free instance, and only then evaluates that module body or resolves it to an importer. Turbopack and webpack therefore omit inactive locale catalogs and unvisited route messages from network requests. Locale changes require a document navigation.

Eager translation calls must execute inside a function, method, or callback after i18n activation. Palamedes rejects eager macros at module scope, and declarations that defer translation until component render remain valid. The client bootstrap also initializes the module's own fragment before its body, so custom compiled-adapter calls observe that fragment if they must run eagerly.

Selected .po imports remain normal development dependencies. Their source-locale fallbacks and the Palamedes config are also registered as loader dependencies, so catalog and fallback-policy edits invalidate the affected subset under supported hosts. If a custom loader host does not implement addDependency(), Palamedes emits one development warning; restart after changing a fallback catalog or config in that host. Next may apply Fast Refresh or fall back to a full document reload at an async-module boundary. A document reload is the supported fallback. The development invalidation regression runs under Turbopack. Webpack's top-level-await client build is covered in production, but does not claim an equivalent HMR contract.

Each production fragment import is attempted once. If one rejects, Palamedes logs the failure with the module path and locale, skips only that fragment, and continues hydrating the client graph (including any other fragments that did load). It deliberately avoids an immediate, unbacked-off retry: deterministic CDN, ad-blocker, and stale-deploy failures are unlikely to improve in the same turn, while a retry can add a request without restoring the graph. Development remains fail-fast so catalog wiring failures stay visible while editing. Production retains readable source text by default when a skipped fragment has no loaded translation. Set keepSourceFallbacks: false when the smaller hash-only output is required because authored source text cannot ship.

messageSplitting currently supports PO catalogs and defaults to false for compatibility. Keep using createClientCatalogBoundary() from @palamedes/react/client when an app needs a complete active-locale catalog or a custom loading strategy. Parser-free split apps should author client messages with macros or compiled adapters; raw ICU strings passed to compatibility runtime components still require the full parser.

Create one Next server scope at module level and activate a fresh i18n instance during request-local server initialization. Its lifetime is the complete App Router render, including the RSC pass, Client Component server prerender, and React suspension/resumption. Next render objects are held as weak request keys; there is no process-global "last request" instance to leak another locale.

Do not call setServerI18nGetter() inside every Server Component render. Use serverI18n.run(i18n, callback) only for tightly scoped helper callbacks. Use the generic createServerI18nScope() from @palamedes/runtime/server for classic Node request handlers outside Next. Both server subpaths are Node-only, so keep them out of Client Components and Edge runtime code.

The Next render-lifetime adapter supports the package's declared Next 16 peer range and is verified against Next 16.2. It intentionally binds to Next's server render storage because public React async context does not span both App Router render passes. If a future Next 16 release removes that server storage module, the import fails during the application build instead of silently falling back to stale or cross-request i18n state; upgrade Palamedes before adopting that Next release.

References: Next.js Server and Client Components, Next.js data fetching and request-scoped React cache, and React cache.

Server Functions and Actions

A Server Function starts a separate request, so initialization performed while rendering a page does not cover it. Add a conventional server entry module in the project root or src directory:

// src/palamedes.server.ts
import { createI18n } from "@palamedes/core/compiled";
import { getLocale, serverI18nScope } from "./lib/i18n.server";

export async function initializeServerFunctionI18n(): Promise<void> {
  const { locale } = await getLocale();
  const i18n = createI18n();
  i18n.activate(locale);
  serverI18nScope.activate(i18n);
}

Then opt into automatic initialization with a flag:

const { withPalamedes } = require("@palamedes/next-plugin");

module.exports = withPalamedes(
  {},
  {
    serverFunctions: true,
  },
);

Palamedes instruments directive-visible async functions: direct exports and locally declared named exports in a module with a top-level "use server" directive, async callbacks nested in an exported initializer such as export const save = withAuth(async () => ...), and async functions with their own "use server" directive. It injects one initializer import per module and awaits the initializer after the function's directive prologue. This also covers actions without a local macro; sync and async helper calls then inherit the initialized request scope.

A re-export such as export { save } from "./save" has no function body to instrument at the re-export site. Put "use server" in the implementation module or on the implementation function itself. Exported wrappers can pass a module-local async function or const async arrow/function callback by reference (including through nested wrappers). A wrapper that only receives an imported callback still has no local async body for Palamedes to instrument, so mark that callback's implementation explicitly.

The initializer belongs to the application. It should resolve the request locale, create and activate a fresh request-local i18n instance, and be request-memoized or otherwise idempotent. It does not load a whole locale catalog: for each message-bearing server module, the transform registers one lazy import per locale containing only that module's compiled ids. Static ESM imports naturally bring along registrations from transitive helpers. After the application initializer activates its instance, Palamedes imports only the active locale's registered fragments and loads them into that instance.

Registration must happen before the initializer runs to affect the current request. A module first reached through a dynamic import inside the action body registers its fragments too late for that invocation; those registrations are available to subsequent requests. Keep translating helpers in the static ESM dependency graph, or load their messages explicitly before translating during the first request.

Generated locale imports are deduplicated across concurrent and later requests by the server module runtime. The request-local load() calls still merge each fragment into the fresh instance; they scale with the messages represented in the currently evaluated server graph rather than with the complete locale catalog. The plugin resolves exactly one palamedes.server module from the project root or src directory and keeps its absolute import address internal.

Registration follows module evaluation, not a per-action bundler manifest. A long-lived server runtime can therefore retain registrations from more than one action graph, so a later action may load a superset of its own dependency closure. This affects the upper performance bound, not lookup correctness: Palamedes still imports only the active locale and only the selected ids from each registered source module. During webpack development, each generated server module releases its exact registration on HMR disposal, and a re-evaluated module atomically replaces all of its sidecars. Turbopack does not currently expose an equivalent server-module disposal hook to loader output; edits and catalog/config changes replace active registrations, but a module removed from the graph can remain registered until the dev server restarts.

Parameter defaults execute before the function body. Palamedes therefore rejects eager macros in Server Function parameter initializers, including nested destructuring defaults. Move such defaults into the body and preserve JavaScript default-parameter semantics explicitly:

export async function save(message?: string) {
  "use server";
  if (message === undefined) message = t`Fallback`;
}

Do not replace this guard with ??= unless null should also select the fallback. Server Function instrumentation is opt-in and currently targets the Next.js integration.

Options

const { withPalamedes } = require("@palamedes/next-plugin");

module.exports = withPalamedes(
  {},
  {
    include: /\.([cm]?[jt]s|[jt]sx)$/,
    exclude: /node_modules/,
    enablePoLoader: true,
    configPath: "./palamedes.yaml",
    projectRoot: undefined,
    failOnMissing: false,
    failOnCompileError: false,
    keepSourceFallbacks: undefined,
    workspaceRoot: undefined,
    serverFunctions: true,
    messageSplitting: true,
  },
);

keepSourceFallbacks defaults to true in both development and production, so a missing catalog fragment renders readable source text rather than a compiled hash. Set it to false to opt into smaller output or prevent source text from shipping. The parser-free runtime leaves ICU source fallbacks raw; use @palamedes/core when such a fallback must interpolate values.

include and exclude select which sources are macro-transformed, and apply under both bundlers: webpack uses them as the loader's test/exclude, and Turbopack receives them as { path: include } plus { not: { path: exclude } } in the rule condition.

The two bundlers do not match the same string, so a regex that is anchored to a directory layout can behave differently:

  • webpack tests the absolute resource path (/home/me/app/src/page.tsx)
  • Turbopack tests its own internal path representation for the module, which is not guaranteed to be that absolute OS path

Patterns matching a file extension (/\.([cm]?[jt]s|[jt]sx)$/) or a path segment (/[/\\]generated[/\\]/) work the same under both. Patterns anchored with ^, or built from an absolute directory, are the ones that can match under webpack and silently miss under Turbopack — prefer segment-based patterns and verify under both bundlers before relying on one. Both bundlers skip dependencies by default: exclude defaults to /node_modules/, and the Turbopack rule also carries { not: "foreign" }.

The .po loader is scoped the same way. It is registered with { not: "foreign" } under Turbopack and exclude: /node_modules/ under webpack, so a dependency that ships importable .po files is left alone instead of failing the build as an unmatched catalog.

projectRoot pins the Next application directory. Under the normal Next CLI, Palamedes derives it from next dev apps/web / next build apps/web; webpack and Turbopack loaders also prefer their supplied Next root context. This makes config discovery, palamedes.server.*, catalog paths, and cache entries belong to the app rather than the shell's working directory. Relative configPath values resolve from this directory. Set projectRoot explicitly in a custom Next host or if the app directory is ambiguous. cwd is a deprecated alias.

workspaceRoot pins the monorepo root used for Turbopack and output file tracing. When omitted, withPalamedes walks upward from the Next project root looking for workspace markers (workspaces in package.json, pnpm-workspace.yaml, turbo.json, or .git) and — when it finds one — sets outputFileTracingRoot and turbopack.root on the Next config as a side effect. Pass workspaceRoot explicitly if that detection picks the wrong directory.

What This Package Handles

  • transforms supported message macros in JavaScript and TypeScript sources
  • compiles imported .po files into JavaScript modules
  • keeps source-string-first catalog semantics aligned with the native core
  • reports placeholder and ICU compatibility diagnostics from the native catalog compiler
  • integrates with both webpack and Turbopack

Related Docs

palamedes is part of the Ferramenta family — Rust-native developer tools that keep the APIs the ecosystem already knows.

Siblings: ferroni · ferriki · ferromark · ferrolex · ferrocat · ferrovia · ferralk · ferrugo.

License

Sebastian Software

MIT OR Apache-2.0 © 2026 Sebastian Software