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

server-actions-for-next-pages

v2.1.1

Published

Use Next.js server actions in your pages directory

Readme

This Next.js plugin let you use something like Next.js Server Actions with the /pages directory, letting you call server functions directly from your client components.

WIth Server Actions i mean calling your functions that run in the server directly in your client components, it does not closely follow the Next.js Server Actions behavior.

Differences with Next.js Server Actions

  • Actions can be imported inside pages and app files

  • Actions must be defined in

    • a file inside the /pages/api directory with the "poor man's use server" directive on top
    • a route inside the /app directory with the "poor man's use server" directive on top
  • No closure support, actions can be defined for an entire file(adding "poor man's use server" at the top of the file)

  • Actions can run concurrently

  • Actions can throw errors, on the client these errors will be thrown with the same error message

  • Actions inputs and outputs are serialized with superjson, a superset of JSON

  • Actions do not work inside formAction, you call the function inside onSubmit instead

  • To get headers and cookies you cannot import them directly from next/headers, instead you have to use getContext:

    "poor man's use server";
    import { cookies, headers } from 'server-actions-for-next-pages/headers';
    
    export async function action({}) {
      return { headers: headers(), cookies: cookies() };
    }

Installation

npm i server-actions-for-next-pages

Usage

Add the plugin to your next.config.js file:

// next.config.js
const { withServerActions } = require('server-actions-for-next-pages');

/** @type {import('next').NextConfig} */
const nextConfig = withServerActions()({
  reactStrictMode: false,
});

module.exports = nextConfig;

Create a file for your server actions inside the /pages/api directory with "poor man's use server" at the top:

// pages/api/server-actions.js
"poor man's use server";

export async function serverAction() {
  return { hello: 'world' };
}

Import your actions in your client components:

// pages/index.jsx
import { serverAction } from './api/server-actions';

export default function Page() {
  serverAction().then((data) => console.log(data));

  return <div>...</div>;
}

Usage in edge runtime

This plugin assumes the runtime of your app to be Nodejs unless you explicitly set it to edge for your api page, this means that to support the edge runtime you need to export a config object like export const config = { runtime: 'edge' }; in your api page.

// pages/api/server-actions.js
"poor man's use server";

export const runtime = 'edge';

export async function serverAction() {
  return { hello: 'world' };
}

Accessing the request and response objects

This plugin injects the req and res objects in an AsyncLocalStorage context, so you can access them in your server functions:

Edge function example:

"poor man's use server";

import { cookies, headers } from 'server-actions-for-next-pages/headers';

export const runtime = 'edge';

export async function serverAction({}) {
  const host = headers().get('host');
  return { host };
}

Example in Node.js:

"poor man's use server";
import { cookies, headers } from 'server-actions-for-next-pages/headers';

export async function createUser({ name = '' }) {

  const host = headers().get('host');

  return {
    name,
    host,
  };
}

Adding error logging and handling

You can export a function named wrapMethod to easily wrap all your server actions with error logging or other wrappers

"poor man's use server";

export function wrapMethod(fn) {
  return async (...args) => {
    try {
      const res = await fn(...args);
      return res;
    } catch (error) {
      console.error(error);
      throw error;
    }
  };
}

export async function failingFunction({}) {
  throw new Error('This function fails');
}

Aborting requests with AbortSignal

You can abort ongoing server actions using AbortController and AbortSignal. Pass an abort signal in your arguments (either directly or as a field in an object parameter), and it will be used to:

  • Client-side: Abort the fetch request
  • Server-side: Allow server functions to respond to request cancellations
// pages/api/server-actions.js
"poor man's use server";

export async function longRunningTask(signal: AbortSignal) {
  for (let i = 0; i < 10; i++) {
    // Check if the request was aborted
    if (signal.aborted) {
      throw new Error('Task aborted: ' + signal.reason);
    }
    await sleep(1000);
  }
  return { completed: true };
}

// Also works with async generators
export async function* streamData({ signal }: { signal: AbortSignal }) {
  for (let i = 0; i < 20; i++) {
    if (signal.aborted) {
      throw new Error('Stream aborted: ' + signal.reason);
    }
    await sleep(500);
    yield { count: i };
  }
}

Client usage:

// pages/index.tsx
import { longRunningTask, streamData } from './api/server-actions';

export default function Page() {
  const handleAbortableTask = async () => {
    const controller = new AbortController();
    
    // Abort after 2 seconds
    setTimeout(() => controller.abort('Timeout'), 2000);

    try {
      await longRunningTask(controller.signal);
    } catch (error) {
      console.log(error); // "Task aborted: Timeout"
    }
  };

  const handleAbortableStream = async () => {
    const controller = new AbortController();
    const generator = streamData({ signal: controller.signal });
    
    for await (const { count } of generator) {
      console.log(count);
      if (count > 5) {
        controller.abort('User stopped stream');
        break;
      }
    }
  };

  return <div>...</div>;
}

Note: You can pass AbortSignal or AbortController directly as arguments, or as fields within object parameters. The server function will receive the request's abort signal, allowing it to detect when the client cancels the request (e.g., when navigating away).

Custom fetch function

You can provide a custom fetch function to your server actions by including it as a fetch field in an object parameter. This is useful for adding custom headers, authentication, or using a custom fetch implementation.

// pages/api/server-actions.js
"poor man's use server";

export async function fetchExternalData({ url, fetch }) {
  // The fetch parameter will be either:
  // - The custom fetch function passed from the client
  // - The global fetch function (injected automatically on the server)
  const response = await fetch(url);
  return response.json();
}

Client usage with custom fetch:

// pages/index.tsx
import { fetchExternalData } from './api/server-actions';

export default function Page() {
  const handleFetch = async () => {
    // Define a custom fetch with authentication
    const customFetch = (url, options) => {
      return fetch(url, {
        ...options,
        headers: {
          ...options?.headers,
          'Authorization': 'Bearer my-token',
        },
      });
    };

    const data = await fetchExternalData({ 
      url: 'https://api.example.com/data',
      fetch: customFetch 
    });
    console.log(data);
  };

  return <div>...</div>;
}

How it works:

  • Client-side: If you provide a fetch field in an object parameter, it will be used for the RPC call instead of the global fetch
  • Server-side: The global fetch function is automatically injected into object parameters (unless you already provided your own)
  • User-provided fetch functions always take precedence over the injected one

How it works

The plugin will replace the content of files inside pages/api with "poor man's use server" at the top to make the exported functions callable from the browser.

When processing the file for the server the plugin creates an API handler that follows the JSON RPC spec.

When processing the file for the client the plugin replaces the exported functions with a fetch calls to the API handler.

This plugin uses Babel to process your page content, it will not slow down compilation time noticeably because it only process files inside the pages/api folder.

Credits

This is a fork of the awesome next-rpc with some changes:

  • It supports the Edge runtime
  • Uses superjson to serialize and deserialize arguments and results
  • It sets status code to 502 when the server function throws an error
  • It uses the top level "poor man's use server" instead of the config.rpc option
  • wrapMethod can be defined with an export instead of config.wrapMethod