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

@zvk/next-kit

v0.2.6

Published

Next App Router adapters for server actions, cache invalidation, form data, and route handlers.

Readme

@zvk/next-kit

Next App Router adapters for server actions, cache invalidation, FormData parsing, and route handlers.

import { formDataToObject } from "@zvk/next-kit";
import {
  createFormDataMutationAction,
  createMappedNextMutationAction,
  createNextMutationAction,
  createZodNextMutationAction,
  parseFormDataActionInput,
  parseZodFormDataActionInput,
  createPassthroughNextMutationAction,
  createPassthroughNextMutationActionFactory,
  createZodFormDataMutationAction,
  createZodPassthroughNextMutationAction,
  createZodPassthroughNextMutationActionFactory,
  type ParseFormDataActionInput,
  type ParseZodFormDataActionInput,
} from "@zvk/next-kit/actions";
import { createNextRouteHandler, jsonErrorResponse, parseJsonRequestBody, parseSearchParams, parseZodJsonRequestBody, parseZodSearchParams, readJsonRequestBody, readRawRequestBody, readRequestBody } from "@zvk/next-kit/route-handlers";
import { captureRedirectPath, createJsonRequest, createMutationActionScenario, createNextNavigationRedirectMock, createRouteHandlerContext } from "@zvk/next-kit/test-utils";
import { buildAppUrl, safeNextPath } from "@zvk/next-kit/url";

The package keeps its runtime helpers narrow. It depends on @zvk/contracts, @zvk/feature-kit, @zvk/server-kit, server-only, and a Next 15 || 16 peer, but it does not add React components, React Query, Supabase, Drizzle, or app-specific policy.

See ../../docs/package-boundary-matrix.md for the package-family runtime boundary matrix. The actions, cache, and route-handlers subpaths are server-only; the root export stays browser-safe.

@zvk/next-kit/actions intentionally keeps both layers of action helpers. Use createNextMutationAction when you want the canonical contract ActionResult wrapper from @zvk/contracts, and use the mapped or passthrough helpers when the app owns the flattened result envelope or needs app-owned wrapper output. Use createZodNextMutationAction for the same canonical ActionResult shape when the app already owns a Zod-like schema and should not compose a schema adapter locally.

Use createFormDataMutationAction or createZodFormDataMutationAction for ordinary <form action> mutations that should parse FormData, return a typed ActionResult, map validation and dependency failures to ContractError, run the mutation, revalidate only after success, and also expose a void formAction for React form props. For canonical mutation helpers, pass shouldRevalidate with revalidate when successful mutations can return a no-op result that should not invalidate cache targets.

Use @zvk/next-kit/cache for path and tag cache invalidation in Next App Router server code. Path targets call revalidatePath; tag targets call revalidateTag. The package expects the Next 15 or 16 next/cache APIs.

Use @zvk/next-kit/url for framework-safe URL construction, next-path sanitization, and search-param appending. Redirect destinations and route names remain application policy. safeNextPath accepts unknown external values, including form-derived values, and falls back unless the input is a same-origin local path.

Use createMappedNextMutationAction when an application owns a flattened action result envelope and needs to map shared contract success/error results into that shape.

Use parseFormDataActionInput when a redirecting or custom server action needs to convert FormData and validate it through a schema adapter while keeping redirect destinations, auth policy, and product copy in the app. Use parseZodFormDataActionInput when the app already validates with Zod-like schemas and should not compose the schema adapter locally. Use ParseFormDataActionInput or ParseZodFormDataActionInput for app-local wrappers that preserve package parser input typing while adding app-owned redirect policy or response mapping. Pass values for server-owned fields, such as organization IDs or fixed return paths, that must override any submitted form values before validation.

Use createPassthroughNextMutationAction when run already returns the application-owned envelope and package-owned validation, authorization, dependency, or revalidation failures should be mapped into that same envelope. Use createZodPassthroughNextMutationAction when the caller already owns a Zod-like schema and should avoid local schema-adapter glue. When passing revalidate, also pass shouldRevalidate so app-level error envelopes do not accidentally invalidate cache targets. Use createPassthroughNextMutationActionFactory when an app already owns schema adapters and wants to bind its package-error mapper and default app-envelope revalidation predicate once. Use createZodPassthroughNextMutationActionFactory when an app wants to bind its package-error mapper and default revalidation predicate once, then create many app-envelope actions without repeating that wiring.

const createAppMutation = createPassthroughNextMutationActionFactory<
  AppActionResult<Project>,
  AppActionResult<Project>
>({
  mapError: actionErrorFromContractError,
  shouldRevalidate: (result) => result.status === "ok",
});

export const saveProject = createAppMutation({
  schema: projectInputSchemaAdapter,
  getContext: requireProjectContext,
  run: saveProjectInput,
  revalidate: ["/projects"],
});

Keep public action envelopes, redirect targets, auth/session policy, product copy, and route-specific revalidation predicates in the application wrapper.

Use readRequestBody when a custom Route Handler needs content-type-aware JSON, text, form, or empty body parsing while receiving malformed or unsupported body failures as contract validation errors. Use parseJsonRequestBody for streaming or custom-response Route Handlers that cannot use createNextRouteHandler but still need shared JSON parsing and schema validation before returning an app-owned response. Use parseZodJsonRequestBody when the route already owns a Zod-like schema. Use readJsonRequestBody only when the route needs raw body control before app-owned validation. Use parseSearchParams or parseZodSearchParams to normalize URLSearchParams, preserve repeated keys as arrays, and validate query input without repeating route-local conversion code. Use readRawRequestBody for signed webhook routes that need the exact text payload before app-owned signature verification. Use jsonResponse, jsonErrorResponse, and jsonResultResponse for small custom JSON Route Handler responses. Pass mapper functions for app-owned envelopes, product copy, and error details; the package only owns response serialization, status, and headers.

Use createJsonRequest from @zvk/next-kit/test-utils for App Router tests that need deterministic JSON Request objects, including raw malformed bodies. Use createRouteHandlerContext for typed { params } objects and createMutationActionScenario when action tests need deterministic context, authorization, run output, and call history without recreating mocks. Use createNextNavigationRedirectMock and captureRedirectPath when testing redirecting actions that import next/navigation. They let tests mock redirects without adding a dependency on a specific test runner. Use createRedirectTestDouble only when the redirect function is injected directly.

Consumer Recipe Map

These recipes describe how a Next App Router application should combine @zvk/next-kit with @zvk/ui and @zvk/composite. They are app-side recipes, not @zvk/next-kit dependencies. Keep React components, auth providers, billing providers, storage clients, AI SDK transport, table engines, and product copy in the consuming app.

Server Form Surface

Use createZodFormDataMutationAction or createFormDataMutationAction for the server action, and render the form with UI/composite packages from app code:

import { createZodFormDataMutationAction } from "@zvk/next-kit/actions";
import { FormSurface } from "@zvk/composite/form-surface";
import { Alert } from "@zvk/ui/alert";
import { Button } from "@zvk/ui/button";
import { Checkbox } from "@zvk/ui/checkbox";
import { Input } from "@zvk/ui/input";
import { NativeSelect } from "@zvk/ui/native-select";
import { Textarea } from "@zvk/ui/textarea";

const saveProject = createZodFormDataMutationAction({
  schema: projectFormSchema,
  getContext: requireWorkspaceContext,
  authorize: canEditProjects,
  run: saveProjectSettings,
  values: { workspaceId },
  revalidate: ["/projects", { tag: "projects" }]
});

export function ProjectSettingsForm({ error }: { readonly error?: string }) {
  return (
    <FormSurface
      action={saveProject.formAction}
      alert={error ? <Alert tone="destructive" title={error} /> : null}
      footer={<Button type="submit">Save project</Button>}
      title="Project settings"
    >
      <Input label="Project name" name="name" required />
      <NativeSelect label="Visibility" name="visibility" items={visibilityOptions} />
      <Textarea label="Notes" name="notes" />
      <Checkbox label="Enable weekly summary" name="weeklySummary" value="on" />
    </FormSurface>
  );
}

The package parses FormData, maps expected validation/dependency failures, and revalidates after success. The app still owns auth/session lookup, permissions, mutation implementation, redirects, toasts, analytics, and user-facing copy.

Route Handler Envelope

Use route-handler helpers around app-owned context, authorization, and service calls. Keep rate limits, persistence, provider clients, and product envelopes in the route or service layer:

import { jsonErrorResponse, jsonResultResponse, parseZodJsonRequestBody } from "@zvk/next-kit/route-handlers";

export async function POST(request: Request): Promise<Response> {
  const parsed = await parseZodJsonRequestBody({
    request,
    schema: createProjectRequestSchema,
    invalidMessage: "Project request is invalid"
  });

  if (!parsed.ok) {
    return jsonErrorResponse(
      { status: 400, message: parsed.error.message, code: parsed.error.code },
      (error) => ({ status: "error", message: error.message, code: error.code })
    );
  }

  const context = await requireWorkspaceContext(request);
  const result = await createProjectFromRequest(parsed.data, context);

  return jsonResultResponse(result, {
    mapOk: (data) => ({ status: "ok", data }),
    mapError: (error) => ({ status: "error", message: error.message, code: error.code }),
    okStatus: 201
  });
}

For routes that fit the shared handler shape, use createNextRouteHandler. For streaming, multipart, custom status handling, or provider-specific errors, use parseJsonRequestBody, parseZodJsonRequestBody, readRequestBody, jsonResponse, jsonErrorResponse, and jsonResultResponse directly.

File Upload Surface

Compose upload UI from @zvk/ui/file-dropzone, @zvk/ui/file-upload-input, and @zvk/composite/upload-manager-surface. Use @zvk/next-kit/actions or @zvk/next-kit/route-handlers only for the app-owned upload flow:

  • UI owns file selection, drag/drop affordances, progress rows, errors, and retry/delete button placement.
  • The app owns storage provider choice, signed URL creation, object keys, metadata persistence, file-type and size policy, malware scanning, retries, deletion, and audit events.
  • Supabase-specific apps can use @zvk/supabase-kit in the app layer without making UI or composite packages depend on Supabase.

AI Chat Workbench

Compose chat and review surfaces from @zvk/ui/conversation, @zvk/ui/tool-call-status, @zvk/ui/source-reference-list, @zvk/ui/diff-viewer, @zvk/composite/conversation-directory, @zvk/composite/context-selection-basket, @zvk/composite/sticky-composer, @zvk/composite/provider-model-selector, @zvk/composite/generated-artifact-workbench, and @zvk/composite/review-decision-card.

The app owns AI SDK transport, streaming, model catalogs, prompt assembly, tool execution, context retrieval, citations, persistence, usage limits, and error recovery. @zvk/next-kit can help route handlers parse JSON and return envelopes, but it should not depend on an AI SDK.

Data Table And Index Page

Use @zvk/composite/data-table-page-frame and @zvk/composite/data-table-control-bar for layout, then place @zvk/ui/table, @zvk/ui/pagination, @zvk/ui/empty-state, @zvk/ui/input, and @zvk/ui/applied-filter-list inside the app-owned slots.

Keep row models, sorting, filtering, query state, URL sync, pagination, selection state, column visibility, saved views, loading, and bulk mutations in the consuming app. ZVK packages provide semantic markup and placement, not a table engine.

Auth, Billing, And Access Frames

Use @zvk/composite/account-access-shell, @zvk/composite/access-state-panel, @zvk/composite/option-comparison-grid, @zvk/composite/form-surface, @zvk/ui/alert, @zvk/ui/badge, and @zvk/ui/button for access and plan presentation.

The app owns auth providers, sessions, permissions, plan models, checkout or customer-portal links, premium entitlement, StoreKit or billing SDK policy, and audit events. Do not move those policies into @zvk/composite or @zvk/next-kit.

Root App Setup And Theme Scope

New Next starters should import package styles once at the app root, scope the rendered app, and bind app-owned fonts into ZVK font tokens:

import type * as React from "react";

import "@zvk/ui/styles.css";
import "@zvk/themes/styles.css";
import "@zvk/composite/styles.css";

export default function RootLayout({ children }: { readonly children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <div
          className="zvk-ui-root"
          data-zvk-ui-density="default"
          data-zvk-ui-preset="zinc"
          data-zvk-ui-theme="light"
          style={{
            "--zvk-ui-font-family-sans": "var(--font-app-sans)",
            "--zvk-ui-font-family-mono": "var(--font-app-mono)"
          } as React.CSSProperties}
        >
          {children}
        </div>
      </body>
    </html>
  );
}

Verify root setup with a root-scope check, computed font check, desktop/mobile screenshot review, and an app-local CSS budget review before adding custom layout CSS.

Repo Skill

Use .codex/skills/use-zvk-next-kit/SKILL.md when maintaining this package.

See ../../docs/package-boundary-matrix.md for the browser-safe and server-only subpaths.