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

rpc4next

v0.6.1

Published

Inspired by Hono RPC and Pathpida, rpc4next brings a lightweight and intuitive RPC solution to Next.js, making server-client communication seamless

Readme

rpc4next

Ask DeepWiki

rpc4next is a lightweight, type-safe RPC layer for Next.js App Router projects. It scans your existing app/** files, generates a PathStructure type, and lets you call route handlers through a typed client without introducing a custom server framework.

It is inspired by Hono RPC and Pathpida:

  • route.ts files become typed RPC endpoints
  • page.tsx files become typed URL/path entries
  • dynamic segments and exported route Query types are reflected in generated client types
  • optional generated params.ts files can give route files a stable sibling Params type

If you want to see a full working example, start with the real integration fixture in integration/next-app/README.md. It shows how route scanning, generated types, the client, and a real Next.js app fit together in this repository.

What It Covers

  • Typed client calls for app/**/route.ts
  • Typed URL generation for app/**/page.tsx
  • Dynamic routes, catch-all routes, and optional catch-all routes
  • Route groups and parallel-route descendants
  • Validation helpers for params, query, json, headers, and cookies
  • Plain Next.js route handlers written with NextResponse.json(...) or Response.json(...)

Routing notes:

  • Route group folders do not appear in generated public paths
  • Parallel route slot names are excluded, but their descendant pages are flattened onto public URL paths
  • Intercepting route branches are excluded from PathStructure because rpc4next models public URL paths

This is a good fit if you want typed client calls and typed URLs from an existing App Router codebase without moving to a custom RPC server framework. If you already want to keep writing normal route.ts and page.tsx files, rpc4next is designed for that.

Requirements

  • Node.js >=20.19.2
  • Next.js App Router
  • Package peer dependency support in rpc4next and rpc4next-cli: Next.js ^15 or ^16

Installation

npm install rpc4next
npm install -D rpc4next-cli

If you use Bun in your project:

bun add rpc4next
bun add -d rpc4next-cli

zod is only needed if you use the server-side validation helpers such as zValidator(). If you only use the generated client types and do not validate request input with Zod, you can omit it.

If you want Zod-based request validation later:

npm install zod

Quick Start

If you prefer to inspect a complete app before wiring this into your own project, see integration/next-app/README.md.

1. Define a Route

rpc4next does not require routeHandlerFactory(). It can scan and generate client types from standard Next.js App Router handlers as-is. The server helpers are optional and mainly give you stronger response and validation typing.

If you want the stronger typed server-side experience, use routeHandlerFactory():

// app/api/users/[userId]/route.ts
import { routeHandlerFactory } from "rpc4next/server";

export type Query = {
  includePosts?: "true" | "false";
};

const createRouteHandler = routeHandlerFactory();

export const { GET } = createRouteHandler<{
  params: { userId: string };
  query: Query;
}>().get(async (rc) => {
  const { userId } = await rc.req.params();
  const query = rc.req.query();

  return rc.json({
    ok: true,
    userId,
    includePosts: query.includePosts === "true",
  });
});

Notes:

  • routeHandlerFactory() is optional, not required
  • Export Query from a route if you want the generated client to type searchParams for plain Next.js handlers too
  • routeHandlerFactory() gives you typed helpers such as rc.json(), rc.text(), and rc.redirect()
  • Validation helpers such as zValidator() are optional

If you also want request validation with Zod, add zValidator():

import { routeHandlerFactory } from "rpc4next/server";
import { zValidator } from "rpc4next/server/validators/zod";
import { z } from "zod";

const createRouteHandler = routeHandlerFactory();

const querySchema = z.object({
  includePosts: z.enum(["true", "false"]).optional(),
});

export const { GET } = createRouteHandler<{
  params: { userId: string };
  query: z.infer<typeof querySchema>;
}>().get(zValidator("query", querySchema), async (rc) => {
  const query = rc.req.valid("query");
  return rc.json({ ok: true, includePosts: query.includePosts === "true" });
});

zValidator() validates request input and returns 400 JSON errors by default on invalid input.

2. Generate PathStructure

Generate the client types from your app directory:

npx rpc4next app src/generated/rpc.ts

If you use Bun:

bunx rpc4next app src/generated/rpc.ts

You can also configure the CLI with rpc4next.config.json:

{
  "baseDir": "app",
  "outputPath": "src/generated/rpc.ts",
  "paramsFile": "params.ts"
}

Then run:

npx rpc4next

Or with Bun:

bunx rpc4next

Positional arguments:

  • <baseDir>: the App Router root to scan, such as app
  • <outputPath>: the file to generate, such as src/generated/rpc.ts

Useful options:

  • -w, --watch: regenerate on file changes
  • -p, --params-file [filename]: generate sibling params files such as app/users/[userId]/params.ts

Examples:

npx rpc4next --watch
npx rpc4next app src/generated/rpc.ts --params-file params.ts

3. Create a Client

// src/lib/rpc-client.ts
import { createRpcClient } from "rpc4next/client";
import type { PathStructure } from "../generated/rpc";

export const rpc = createRpcClient<PathStructure>("");

Use "" for same-origin calls in the browser, or pass an absolute base URL for server-side or cross-origin usage.

4. Call Routes

Generated client naming follows the App Router path shape:

  • static segments stay as property access, such as rpc.api.users
  • dynamic segments become callable helpers, such as [userId] -> ._userId("123")
  • route.ts methods become $get(), $post(), and so on
  • page.tsx entries can be turned into typed URLs with $url()
const response = await rpc.api.users._userId("123").$get({
  url: { query: { includePosts: "true" } },
});

const data = await response.json();

For JSON request bodies:

const response = await rpc.api.posts.$post({
  body: { json: { title: "hello" } },
});

For request headers and cookies:

const response = await rpc.api["request-meta"].$get({
  requestHeaders: {
    headers: { "x-integration-test": "example" },
    cookies: { session: "abc123" },
  },
});

5. Generate Typed URLs for Pages

page.tsx files are included in the generated path tree, so you can build typed URLs even when there is no RPC method to call.

const photoUrl = rpc.photo._id("42").$url();

photoUrl.path;
photoUrl.relativePath;
photoUrl.pathname;
photoUrl.params;

Server Helpers

routeHandlerFactory

routeHandlerFactory() creates typed handlers for:

  • get
  • post
  • put
  • delete
  • patch
  • head
  • options

It also supports a shared error handler:

import { routeHandlerFactory } from "rpc4next/server";

const createRouteHandler = routeHandlerFactory((error, rc) => {
  return rc.text("error", 400);
});

export const { POST } = createRouteHandler().post(async (rc) => {
  return rc.json({ ok: true }, 201);
});

zValidator

zValidator() supports these targets:

  • params
  • query
  • json
  • headers
  • cookies

Example:

import { routeHandlerFactory } from "rpc4next/server";
import { zValidator } from "rpc4next/server/validators/zod";
import { z } from "zod";

const createRouteHandler = routeHandlerFactory();

const jsonSchema = z.object({
  title: z.string().min(1),
});

export const { POST } = createRouteHandler().post(
  zValidator("json", jsonSchema),
  async (rc) => {
    const body = rc.req.valid("json");
    return rc.json({ title: body.title }, 201);
  },
);

If you provide a custom hook, you must return a response yourself when validation fails:

zValidator("json", jsonSchema, (result, rc) => {
  if (!result.success) {
    return rc.json({ error: result.error.issues }, 422);
  }
});

Plain Next.js Route Handlers Also Work

You can keep using native App Router handlers without adopting routeHandlerFactory(). This is useful when you want to stay close to stock Next.js APIs and only use rpc4next for route scanning and client generation.

Example with NextResponse.json(...):

// app/api/next-native/[itemId]/route.ts
import { type NextRequest, NextResponse } from "next/server";

export type Query = {
  filter?: string;
};

export async function GET(
  request: NextRequest,
  context: { params: Promise<{ itemId: string }> },
) {
  const { itemId } = await context.params;
  const filter = request.nextUrl.searchParams.get("filter") ?? "all";

  return NextResponse.json({
    ok: true,
    itemId,
    filter,
  });
}

Example with Response.json(...):

// app/api/next-native-response/route.ts
export async function GET() {
  return Response.json({
    ok: true,
    source: "response-json",
  });
}

The generated client can still call this route:

const response = await rpc.api["next-native"]
  ._itemId("item-1")
  .$get({ url: { query: { filter: "recent" } } });

You can also call a plain Response.json(...) route:

const response = await rpc.api["next-native-response"].$get();

For native handlers, route discovery and request typing still work, but response typing is naturally broader than when you return rpc4next's typed helpers.

See integration/next-app/README.md for the repository's full integration fixture coverage and route-pattern notes.

Generated Files

When paramsFile is enabled, the CLI can generate sibling files such as:

// app/api/users/[userId]/params.ts
export type Params = { userId: string };

That lets route files import the param shape instead of repeating it manually. These generated params.ts files are optional, and your generated src/generated/rpc.ts is typically not something you edit by hand.

Your generated src/generated/rpc.ts exports a PathStructure type that includes:

  • path entries from page.tsx
  • callable HTTP methods from route.ts
  • dynamic segment parameter types
  • route Query exports where available

Typical Workflow

  1. Add or update files under app/**
  2. Run rpc4next to regenerate PathStructure
  3. Import PathStructure into your client
  4. Call routes with createRpcClient<PathStructure>(...)
  5. Use routeHandlerFactory and zValidator where you want stronger server-side typing

Repository Layout

  • packages/rpc4next: runtime client and server helpers
  • packages/rpc4next-cli: route scanner and type generator
  • packages/rpc4next-shared: internal shared constants and types
  • integration/next-app: real Next.js integration fixture

If you are evaluating the repository itself, integration/next-app is the best place to see the full flow working in a real app.

License

MIT