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

@prb/effect-next

v1.1.0

Published

Effect-TS integration for Next.js

Readme

effect-next

[!WARNING]

This is experimental, beta software. It is provided "as is" without warranty of any kind, express or implied.

Effect integration for Next.js - build type-safe, composable Next.js applications with Effect.

Features

  • Route Handlers - Convert Next.js route handlers into Effect workflows
  • Server Actions - Type-safe server actions with Effect error handling
  • Middleware - Composable middleware using Effect layers
  • React Hooks - Client-side hooks for running Effects in React components
  • Request-Scoped Cache - Leverage React cache() with Effect for deduplication
  • Request Timing Middleware - Measure request duration with opt-in hooks
  • Environment Helpers - Minimal NODE_ENV helpers with injectable resolver
  • Telemetry Adapters - Optional Sentry + OTLP helpers (no defaults)
  • Headers & Cookies - Access Next.js headers and cookies as Effect services
  • Params - Type-safe route and search params
  • Navigation - Effect-based navigation utilities
  • Testing Kit - Comprehensive testing utilities for Effect-based Next.js apps

Installation

bun add @prb/effect-next effect @effect/platform

Optional Dependencies

  • @effect/opentelemetry for effect-next/telemetry/otel

Quick Start

1. Route Handlers

Convert Next.js route handlers into Effect workflows:

// app/api/users/[id]/route.ts
import { Next } from "effect-next/handlers";
import { Effect } from "effect";
import { RouteParams } from "effect-next/params";

const Route = Next.make("UsersRoute", AppLayer);

export const GET = Route.build(() =>
  Effect.gen(function* () {
    const params = yield* RouteParams;
    const userId = params.id;

    const user = yield* fetchUser(userId);
    return Response.json(user);
  }),
);

2. Server Actions

Create type-safe server actions with automatic error handling:

// app/actions.ts
"use server";

import { runServerAction } from "effect-next/action";
import { Effect } from "effect";

export async function createUser() {
  return runServerAction(
    Effect.gen(function* () {
      const db = yield* Database;
      const user = yield* db.insert(users).values({ name: "Alice" });
      return user;
    }).pipe(Effect.provide(AppLayer))
  );
}

// app/page.tsx
import { createUser } from "./actions";

export default function Page() {
  const handleSubmit = async () => {
    const result = await createUser();
    if (result.success) {
      console.log("User created:", result.data);
      return;
    }
    console.error("Error:", result.error);
  };

  return <button onClick={handleSubmit}>Create User</button>;
}

3. React Hooks

Run Effects in client components:

"use client";

import { useEffectMemo, useEffectNextRuntime } from "effect-next/react-hooks";
import { Effect } from "effect";

function UserProfile({ userId }: { userId: string }) {
  const runtime = useEffectNextRuntime();

  const user = useEffectMemo(
    () => Effect.gen(function* () {
      const api = yield* UserApi;
      return yield* api.getUser(userId);
    }),
    [userId],
    runtime
  );

  if (!user) return <div>Loading...</div>;
  return <div>{user.name}</div>;
}

4. Middleware

Compose middleware using Effect layers:

import { Next } from "effect-next/handlers";
import { RequestTimingMiddleware, makeRequestTimingMiddleware } from "effect-next/middleware/request-timing";
import { Effect, Layer } from "effect";

const AppLayerWithTiming = Layer.mergeAll(AppLayer, makeRequestTimingMiddleware());
const Route = Next.make("RouteWithTiming", AppLayerWithTiming).middleware(RequestTimingMiddleware);

export const GET = Route.build(() =>
  Effect.gen(function* () {
    return Response.json({ ok: true });
  }),
);

5. Request-Scoped Cache

Use React's cache() with Effect for request deduplication:

// lib/data.ts
import { reactCache } from "effect-next/react-cache";
import { Effect } from "effect";

export const getUser = reactCache((id: string) =>
  Effect.gen(function* () {
    const db = yield* Database;
    return yield* db.query("SELECT * FROM users WHERE id = ?", [id]);
  }).pipe(Effect.provide(AppLayer)),
);

// Multiple components can call getUser() in the same request
// but the query will only execute once

API Reference

Route Handlers

import { Next } from "effect-next/handlers";

const Route = Next.make("Route", layer);

export const GET = Route.build(() => effect);
export const POST = Route.build(() => effect);

Server Actions

import { runServerAction, runServerActionOrThrow } from "effect-next/action";

export const myAction = () => runServerAction(effect.pipe(Effect.provide(layer)));
export const myActionOrThrow = () => runServerActionOrThrow(effect.pipe(Effect.provide(layer)));

React Hooks

import {
  EffectNextProvider,
  useEffectNextRuntime,
  useEffectMemo,
  useEffectOnce,
  useForkEffect,
  useStream,
  useStreamLatest,
  useSubscriptionRef,
} from "effect-next/react-hooks";

// Provide runtime to app
<EffectNextProvider runtime={runtime}>
  {children}
</EffectNextProvider>

// Access runtime in components
const runtime = useEffectNextRuntime();

// Run Effect with dependencies
const data = useEffectMemo(() => effect, [deps], runtime);

// Run Effect once on mount
const data = useEffectOnce(effect, runtime);

// Run Effect in background
useForkEffect(effect, runtime, [deps]);

// Subscribe to Stream
const values = useStream(stream, runtime);
const latest = useStreamLatest(stream, runtime, initialValue);

// Subscribe to SubscriptionRef
const value = useSubscriptionRef(ref, runtime);

React Cache

import { Effect } from "effect";
import { reactCache } from "effect-next/react-cache";

const getUser = reactCache((id: string) => effect);
const user = await Effect.runPromise(getUser("user-1"));

Headers & Cookies

import { Headers, Cookies } from "effect-next/headers";

Effect.gen(function* () {
  const headers = yield* Headers;
  const userAgent = headers.get("user-agent");

  const cookies = yield* Cookies;
  const sessionId = cookies.get("sessionId");
});

Params

import { RouteParams, SearchParams } from "effect-next/params";

Effect.gen(function* () {
  const params = yield* RouteParams;
  const userId = params.id;

  const searchParams = yield* SearchParams;
  const page = searchParams.page;
});

Navigation

import { redirect, rewrite, notFound } from "effect-next/navigation";

Effect.gen(function* () {
  yield* redirect("/login");
  yield* rewrite("/new-path");
  yield* notFound();
});

Environment

import { isProduction, resolveEnvironment } from "effect-next/env";

const env = resolveEnvironment();
if (isProduction()) {
  console.log("Production:", env);
}

Telemetry

import { Effect } from "effect";
import { createTelemetryLayer, TelemetryService } from "effect-next/telemetry";

const layer = createTelemetryLayer({
  captureException: (error) => console.error(error),
  captureMessage: (message) => console.log(message),
});

const program = Effect.gen(function* () {
  const telemetry = yield* TelemetryService;
  yield* telemetry.captureMessage("Telemetry ready");
}).pipe(Effect.provide(layer));

Testing Kit

import {
  assertRight,
  assertLeft,
  expectTaggedFailure,
  expectDefect,
  runExpectSuccess,
  runExpectFailure,
  makeMockRuntime,
} from "effect-next/testing-kit";

// Test success cases
test("should succeed", async () => {
  const exit = await Effect.runPromiseExit(effect);
  const value = assertRight(exit);
  expect(value).toBe(42);
});

// Test failure cases
test("should fail with NotFound", async () => {
  const exit = await Effect.runPromiseExit(effect);
  expectTaggedFailure(exit, "NotFound");
});

// Run effects in tests
test("should create user", async () => {
  const user = await runExpectSuccess(createUser(), runtime);
  expect(user.name).toBe("Alice");
});

Project Structure

effect-next/
├── src/
│   ├── action/          # Server actions
│   ├── cache/           # Request-scoped cache
│   ├── env/             # Environment helpers
│   ├── handlers/        # Route handlers
│   ├── headers/         # Headers & cookies
│   ├── middleware/      # Middleware
│   ├── navigation/      # Navigation utilities
│   ├── params/          # Route & search params
│   ├── react-cache/     # React cache integration
│   ├── react-hooks/     # Client-side hooks
│   ├── runtime/         # Runtime utilities
│   ├── server-actions/  # Server action helpers
│   ├── telemetry/       # Telemetry adapters
│   ├── testing-kit/     # Testing utilities
├── tests/               # Test suite
├── package.json
├── tsconfig.json
└── README.md

Examples

See the examples directory for complete examples:

  • Basic route handlers
  • Server actions with form handling
  • Client components with hooks
  • Middleware composition
  • Testing patterns

Contributing

Contributions are welcome! Please read CONTRIBUTING.md for guidelines.

License

MIT

Related Projects

Credits

Built by the Sablier team with inspiration from the Effect community.