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 🙏

© 2025 – Pkg Stats / Ryan Hefner

next-server-actions

v1.0.4

Published

Type-safe server action handling for Next.js — effortless server action validation with Zod, built-in middleware, and great developer ergonomics.

Readme

next-server-actions 📝

Type-safe server action handling for Next.js — effortless server action validation with Zod, built-in middleware, and great developer ergonomics.

next-server-actions is a lightweight utility designed to make working with Next.js Server Actions easier and more enjoyable. It provides a clean API for managing form submissions, validation, middleware, and error handling — all with minimal boilerplate.


✨ Features

  • Server Action Integration – Built specifically for Server Actions in Next.js
  • ⚠️ Field & Form-Level Errors – Handle validation like a pro
  • 🔄 Loading State Management – Easily show loading indicators during submission
  • 🔐 Middleware Support – Add authentication, authorization, or custom checks per action
  • 🔁 DRY-Friendly – Avoid repeating boilerplate in your server logic

Table of contents


Getting Started

1. Installation

npm install next-server-actions

2. Create a reusable server action client

This creates a createServerAction() function that can be reused for all your form actions

import { createClient } from "next-server-actions";

export const createServerAction = createClient({
  // Optional: add middleware here (e.g. auth, logging, etc.)
});

3. Define a Zod schema and server action

This validates the form on the server using the schema before executing any logic.

"use server";

import { createServerAction } from "../utils/server-actions";
import { z } from "zod";

const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

export const signIn = createServerAction(schema, async (values) => {
  // Your login logic here
  return { ok: true };
});

4. Use the server action in your component

This uses useActionState to bind your form to the server action.

"use client";

import Form from "next/form";
import { useActionState } from "react";
import { signIn } from "../_lib/actions/sign-in";

export function SignInForm() {
  const [state, action, pending] = useActionState(signIn, {
    ok: false,
  });

  return (
    <Form action={action}>
      <label htmlFor="email">Email</label>
      <input type="email" id="email" name="email" required />

      <br />

      <label htmlFor="password">Password</label>
      <input type="password" id="password" name="password" required />

      <hr />

      <button type="submit" disabled={pending}>
        {pending ? "Submitting..." : "Submit"}
      </button>
    </Form>
  );
}

Middleware

Middleware allows you to intercept and validate requests before they reach your server action logic. This is useful for enforcing authentication, role-based access control, or other custom request checks.


Adding Middleware

You can define middleware by using the middleware option when creating your server action client. This function runs before your server action executes and can return a response to short-circuit the action—for example, if a user is not authenticated.

import { createClient } from "next-server-actions";

export const createServerAction = createClient({
  middleware: async () => {
    // replace with your auth logic
    const isAuthenticated = false;

    if (!isAuthenticated) {
      return { message: "unauthorized" };
    }
  },
});

If the middleware returns an object, the corresponding action will not run. Instead, that object will be returned as the response to the client.

Handling Middleware Responses in the UI

The returned message (or other fields) can be accessed via the state from useActionState—just like a regular action response.

"use client";

import Form from "next/form";
import { useActionState } from "react";
import { signIn } from "../_lib/actions/sign-in";

export function SignInForm() {
  const [state, action, pending] = useActionState(signIn, {
    ok: false,
  });

  return (
    <Form action={action}>
      {/* Form-level error from middleware */}
      {!state.ok && state.message && <p>{state.message}</p>}

      <label htmlFor="email">Email</label>
      <input type="email" id="email" name="email" />

      <br />

      <label htmlFor="password">Password</label>
      <input type="password" id="password" name="password" />

      <hr />

      <button type="submit">Submit</button>
    </Form>
  );
}

Context

The context feature allows you to inject shared data into all your server actions.

For example, instead of calling getUser() in every individual action, you can call it once in the context function. The returned object will be automatically passed as the second argument to all server actions.

This helps reduce duplication and keeps your action logic clean and focused.


Defining Context

You can define a shared context using the context option when creating your server action client. The context function must return an object, which can contain any data your server actions might need.

import { createClient } from "next-server-actions";
import { getUser } from "../../_data/get-user";

export const createServerAction = createClient({
  context: async () => {
    const user = await getUser();

    return { user };
  }
});

Accessing Context in Actions

The context object you return becomes the second argument in all server action handlers.

"use server";

import { createServerAction } from "../utils/server-actions";
import { z } from "zod";

const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

export const signIn = createServerAction<typeof schema>(
  schema,
  async (values, { user }) => {
    // Your login logic using the context-provided user
    return { ok: true };
  },
);

With this setup, you only need to define logic like getUser() once, making your actions more reusable and maintainable.

shadcn/ui

Easily integrate server actions with your UI using shadcn/ui components.

👉 Check out the simple guide