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

@periodic/strontium-next

v1.0.0

Published

Production-grade Next.js integration for @periodic/strontium — App Router, Pages Router, Edge, and Middleware all covered

Readme

🔺 Periodic Strontium Next

npm version License: MIT TypeScript

Production-grade Next.js integration for @periodic/strontium — App Router, Pages Router, Edge, and Middleware all covered

Part of the Periodic series of Node.js packages by Uday Thakur.


💡 Why Strontium Next?

@periodic/strontium-next is the Next.js integration layer for @periodic/strontium. It brings Strontium's resilience primitives — retry, circuit breaking, deduplication, and timeout control — into every corner of your Next.js application: Server Components, Route Handlers, Middleware, Edge functions, and the Pages Router.

Next.js introduces unique HTTP challenges that generic clients don't address. Headers need to be forwarded from the incoming request to upstream services. Errors need to be mapped to HTTP status codes at the route handler boundary. Cache tags need to be attached to requests and invalidated after mutations. Edge runtime compatibility rules out Node.js-specific APIs. Strontium Next handles all of it.

The name represents:

  • Coverage: Every Next.js runtime and routing system is supported
  • Transparency: Trace headers, correlation IDs, and auth tokens flow through automatically
  • Precision: Strontium errors map to correct HTTP status codes at every boundary
  • Simplicity: One package, one pattern, regardless of which Next.js feature you're using

Just as @periodic/strontium makes your HTTP layer reliable, @periodic/strontium-next makes that reliability native to Next.js — without fighting the framework.


🎯 Why Choose Strontium Next?

Next.js applications make HTTP calls in more places than most frameworks — and generic HTTP clients don't account for any of them:

  • Server Components need trace headers from the incoming request forwarded automatically
  • Route Handlers need Strontium errors mapped to correct HTTP status codes — not unhandled 500s
  • Middleware needs idempotency keys and trace context injected before requests reach handlers
  • Edge runtime rules out Node.js APIs, breaking most HTTP client solutions
  • ISR / cache tags need to be attached to requests and invalidated after mutations
  • Pages Router needs a different client setup than App Router

Periodic Strontium Next provides the perfect solution:

Zero extra dependencies — requires only @periodic/strontium and Next.js
App Router — Server Components and Route Handlers fully supported
Pages Router — works identically in API routes
Edge runtime safe — no Node.js APIs, no process usage
Automatic header forwarding — trace IDs, auth tokens, correlation IDs pass through
wrapRouteHandler — maps Strontium errors to correct HTTP status codes
Middleware support — inject request IDs, idempotency keys, and trace context
Cache integrationtaggedRequest and strontiumRevalidateTag for Next.js ISR
Hydration-safe singletongetClientStrontiumClient for browser
Type-safe — strict TypeScript, zero any, throughout
No global state — no side effects on import
Production-ready — non-blocking, never crashes your app


📦 Installation

npm install @periodic/strontium-next @periodic/strontium

Or with yarn:

yarn add @periodic/strontium-next @periodic/strontium

🚀 Quick Start

// app/users/[id]/page.tsx
import { createServerStrontiumClient } from '@periodic/strontium-next';
import { headers } from 'next/headers';

export default async function UserPage({ params }: { params: { id: string } }) {
  const client = createServerStrontiumClient({
    baseURL: process.env.API_BASE_URL!,
    request: new Request('https://placeholder', {
      headers: Object.fromEntries(headers().entries()),
    }),
  });

  const res = await client.request<User>({
    method: 'GET',
    url: `/users/${params.id}`,
  });

  return <UserProfile user={res.data} />;
}

🧠 Core Concepts

createServerStrontiumClient

  • The primary factory for server-side usage
  • Accepts the incoming Request or NextRequest and forwards trace headers automatically
  • Forwards x-request-id, x-correlation-id, traceparent, authorization, and other trace headers
  • All core Strontium config options (retry, circuit breaker, timeout) are supported
  • Use this in Server Components, Route Handlers, and API routes
const client = createServerStrontiumClient({
  baseURL: process.env.API_BASE_URL!,
  request: req, // incoming request — headers forwarded automatically
  retry: { enabled: true, maxAttempts: 3, strategy: 'exponential' },
  circuitBreaker: { failureThreshold: 5, resetTimeoutMs: 30_000 },
});

wrapRouteHandler

  • Maps Strontium errors to correct HTTP status codes at the route boundary
  • No more unhandled 500 responses when the circuit opens or a timeout fires
  • Wraps any App Router route handler function

Design principle:

Errors should be handled at the boundary, not scattered across every handler. wrapRouteHandler is that boundary.

| Error | Status Code | |-------|-------------| | CircuitOpenError | 503 + Retry-After: 30 | | TimeoutError | 504 | | RetryExhaustedError | 502 | | ResponseValidationError | 502 | | IntegrityViolationError | 409 | | NetworkError | 502 | | Unknown | 500 |


✨ Features

🖥️ App Router: Server Components

Fetch data in Server Components with automatic header forwarding:

// app/orders/page.tsx
import { createServerStrontiumClient } from '@periodic/strontium-next';
import { headers } from 'next/headers';

export default async function OrdersPage() {
  const client = createServerStrontiumClient({
    baseURL: process.env.API_BASE_URL!,
    request: new Request('https://placeholder', {
      headers: Object.fromEntries(headers().entries()),
    }),
  });

  const res = await client.request<Order[]>({ method: 'GET', url: '/orders' });
  return <OrderList orders={res.data} />;
}

🛣️ App Router: Route Handlers

Wrap route handlers to get automatic error-to-status-code mapping:

// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { createServerStrontiumClient, wrapRouteHandler, taggedRequest } from '@periodic/strontium-next';

export const GET = wrapRouteHandler(async (req: NextRequest) => {
  const client = createServerStrontiumClient({
    baseURL: process.env.API_BASE_URL!,
    request: req,
  });

  const res = await client.request<UsersResponse>(
    taggedRequest({ method: 'GET', url: '/users' }, ['users'])
  );

  return NextResponse.json(res.data);
});

🔀 Middleware

Inject request IDs, idempotency keys, and trace context before requests reach your handlers:

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { createStrontiumMiddleware } from '@periodic/strontium-next';

const strontiumMiddleware = createStrontiumMiddleware({
  idempotencyNamespace: 'myapp',
  otelPropagation: true,
});

export function middleware(request: NextRequest) {
  return strontiumMiddleware(request, () => Promise.resolve(NextResponse.next()));
}

export const config = { matcher: '/api/:path*' };

⚡ Edge Runtime

Full support for the Edge runtime — no Node.js APIs required:

// app/api/edge/route.ts
export const runtime = 'edge';

import { createEdgeStrontiumClient } from '@periodic/strontium-next/edge';

export async function GET(request: Request) {
  const client = createEdgeStrontiumClient({
    baseURL: process.env.API_BASE_URL!,
    request,
  });

  const res = await client.request<Data>({ method: 'GET', url: '/data' });
  return Response.json(res.data);
}

📄 Pages Router

Works identically in Pages Router API routes:

// pages/api/users/[id].ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { createServerStrontiumClient } from '@periodic/strontium-next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const client = createServerStrontiumClient({ baseURL: process.env.API_BASE_URL! });
  const result = await client.request<User>({ method: 'GET', url: `/users/${req.query.id}` });
  res.status(200).json(result.data);
}

🏷️ Cache Integration

Attach Next.js ISR cache tags to requests and invalidate them after mutations:

import { taggedRequest, strontiumRevalidateTag, strontiumRevalidateTags } from '@periodic/strontium-next';

// Attach cache tags to a request
const res = await client.request<User[]>(
  taggedRequest({ method: 'GET', url: '/users' }, ['users'])
);

// Invalidate after a mutation
await strontiumRevalidateTag('users');
await strontiumRevalidateTags(['users', `user-${id}`]);

🌐 Hydration-Safe Browser Client

A singleton client for browser-side usage that's safe across hydration:

import { getClientStrontiumClient } from '@periodic/strontium-next';

// Returns the same instance across renders — safe for Client Components
const client = getClientStrontiumClient({
  baseURL: process.env.NEXT_PUBLIC_API_URL!,
});

📚 Common Patterns

1. Server Component Data Fetch

import { createServerStrontiumClient } from '@periodic/strontium-next';
import { headers } from 'next/headers';

export default async function ProductPage({ params }: { params: { id: string } }) {
  const client = createServerStrontiumClient({
    baseURL: process.env.API_BASE_URL!,
    request: new Request('https://placeholder', {
      headers: Object.fromEntries(headers().entries()),
    }),
  });

  const res = await client.request<Product>({ method: 'GET', url: `/products/${params.id}` });
  return <ProductDetail product={res.data} />;
}

2. Mutation Route Handler with Cache Invalidation

// app/api/users/route.ts
import { createServerStrontiumClient, wrapRouteHandler, strontiumRevalidateTag } from '@periodic/strontium-next';

export const POST = wrapRouteHandler(async (req: NextRequest) => {
  const client = createServerStrontiumClient({ baseURL: process.env.API_BASE_URL!, request: req });
  const body = await req.json();

  const res = await client.request<User>({ method: 'POST', url: '/users', body });
  await strontiumRevalidateTag('users');

  return NextResponse.json(res.data, { status: 201 });
});

3. Authenticated Server Component

import { createServerStrontiumClient } from '@periodic/strontium-next';
import { headers } from 'next/headers';
import { redirect } from 'next/navigation';

export default async function DashboardPage() {
  const headersList = headers();
  const token = headersList.get('authorization');
  if (!token) redirect('/login');

  const client = createServerStrontiumClient({
    baseURL: process.env.API_BASE_URL!,
    request: new Request('https://placeholder', { headers: { authorization: token } }),
  });

  const res = await client.request<DashboardData>({ method: 'GET', url: '/dashboard' });
  return <Dashboard data={res.data} />;
}

4. Edge Middleware with Trace Propagation

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { createStrontiumMiddleware } from '@periodic/strontium-next';

const strontiumMiddleware = createStrontiumMiddleware({
  idempotencyNamespace: 'myapp',
  otelPropagation: true,
});

export function middleware(request: NextRequest) {
  return strontiumMiddleware(request, () => Promise.resolve(NextResponse.next()));
}

export const config = { matcher: ['/api/:path*', '/dashboard/:path*'] };

5. Severity-Based Error Handling in Route Handlers

import { CircuitOpenError, TimeoutError, RetryExhaustedError } from '@periodic/strontium';
import { wrapRouteHandler } from '@periodic/strontium-next';

export const GET = wrapRouteHandler(async (req: NextRequest) => {
  // Errors are automatically mapped:
  // CircuitOpenError → 503, TimeoutError → 504, RetryExhaustedError → 502
  const res = await client.request({ method: 'GET', url: '/data' });
  return NextResponse.json(res.data);
}, {
  onError: (err) => {
    if (err instanceof CircuitOpenError) sendToPagerDuty(err);
    else if (err instanceof RetryExhaustedError) sendToSlack(err);
  },
});

6. ISR with Cache Tags

// app/api/products/route.ts
import { taggedRequest, strontiumRevalidateTag } from '@periodic/strontium-next';

// GET — attach cache tags for ISR
export const GET = wrapRouteHandler(async (req: NextRequest) => {
  const res = await client.request<Product[]>(
    taggedRequest({ method: 'GET', url: '/products' }, ['products'])
  );
  return NextResponse.json(res.data);
});

// POST — invalidate after mutation
export const POST = wrapRouteHandler(async (req: NextRequest) => {
  const body = await req.json();
  const res = await client.request<Product>({ method: 'POST', url: '/products', body });
  await strontiumRevalidateTag('products');
  return NextResponse.json(res.data, { status: 201 });
});

7. Structured Logging Integration

import { createLogger, ConsoleTransport, JsonFormatter } from '@periodic/iridium';
import { createServerStrontiumClient } from '@periodic/strontium-next';

const logger = createLogger({
  transports: [new ConsoleTransport({ formatter: new JsonFormatter() })],
});

const client = createServerStrontiumClient({ baseURL: process.env.API_BASE_URL! });

client.use({
  onAfterResponse: (ctx, res) => logger.info('http.response', { url: ctx.url, status: res.status }),
  onError: (ctx, err) => logger.error('http.error', { url: ctx.url, error: err.message }),
  onRetry: (ctx, err) => logger.warn('http.retry', { url: ctx.url, error: err.message }),
});

8. Production Configuration

// lib/api-client.ts
import { createServerStrontiumClient } from '@periodic/strontium-next';
import { headers } from 'next/headers';

export function createApiClient(request?: Request) {
  return createServerStrontiumClient({
    baseURL: process.env.API_BASE_URL!,
    request: request ?? new Request('https://placeholder', {
      headers: Object.fromEntries(headers().entries()),
    }),
    timeoutMs: 8_000,
    retry: {
      enabled: true,
      maxAttempts: 3,
      strategy: 'exponential',
      baseDelayMs: 100,
      maxDelayMs: 5_000,
      jitter: true,
      retryOn: ['network', '5xx'],
    },
    circuitBreaker: {
      failureThreshold: 5,
      resetTimeoutMs: 30_000,
    },
  });
}

🎛️ Configuration Options

createServerStrontiumClient Options

| Option | Type | Description | |--------|------|-------------| | baseURL | string | Base URL for all requests | | request | Request \| NextRequest | Incoming request for automatic header forwarding | | ...StrontiumConfig | — | All core createStrontiumClient options supported |

Automatically forwarded headers: x-request-id, x-correlation-id, traceparent, tracestate, authorization

wrapRouteHandler Options

| Option | Type | Description | |--------|------|-------------| | onError | (err: unknown) => void | Optional callback before error response is returned |

createStrontiumMiddleware Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | idempotencyNamespace | string | 'strontium' | Prefix for generated idempotency keys | | otelPropagation | boolean | false | Propagate OpenTelemetry trace context |


📋 API Reference

Server

createServerStrontiumClient(options): StrontiumClient
createEdgeStrontiumClient(options): StrontiumClient  // from '@periodic/strontium-next/edge'
getClientStrontiumClient(options): StrontiumClient

Route Handlers

wrapRouteHandler(handler, options?): RouteHandler

Middleware

createStrontiumMiddleware(options?): NextMiddleware

Cache

taggedRequest(request: RequestOptions, tags: string[]): RequestOptions
strontiumRevalidateTag(tag: string): Promise<void>
strontiumRevalidateTags(tags: string[]): Promise<void>

Types

import type {
  ServerClientOptions,
  EdgeClientOptions,
  RouteHandlerOptions,
  MiddlewareOptions,
} from '@periodic/strontium-next';

🧩 Architecture

@periodic/strontium-next/
├── src/
│   ├── server/                # Server-side client
│   │   ├── client.ts         # createServerStrontiumClient()
│   │   └── headers.ts        # Automatic header forwarding logic
│   ├── edge/                  # Edge runtime client
│   │   └── client.ts         # createEdgeStrontiumClient()
│   ├── browser/               # Browser-side singleton
│   │   └── client.ts         # getClientStrontiumClient()
│   ├── middleware/            # Next.js middleware integration
│   │   └── index.ts          # createStrontiumMiddleware()
│   ├── handlers/              # Route handler utilities
│   │   ├── wrap.ts           # wrapRouteHandler()
│   │   └── errors.ts         # Strontium error → HTTP status mapping
│   ├── cache/                 # Next.js ISR cache integration
│   │   └── index.ts          # taggedRequest, strontiumRevalidateTag
│   ├── types.ts               # TypeScript interfaces
│   └── index.ts               # Public API

Design Philosophy:

  • Server client forwards headers automatically — no manual plumbing
  • wrapRouteHandler is the error boundary — errors never escape as raw 500s
  • Edge client uses only Web APIs — no Node.js dependencies
  • Cache utilities are thin wrappers around Next.js ISR primitives
  • No opinions on your app structure — works with any folder layout

📈 Performance

  • Automatic header forwarding happens at client creation time — zero overhead per request
  • wrapRouteHandler adds a single try/catch — negligible overhead
  • Edge client uses only native Web APIs — no polyfills, no bundle overhead
  • Hydration-safe singletongetClientStrontiumClient creates one instance, not one per render
  • No monkey-patching — clean wrapping only, no prototype mutation

🚫 Explicit Non-Goals

This package intentionally does not include:

❌ Its own HTTP client (use @periodic/strontium)
❌ React hooks (use @periodic/strontium-react)
❌ A cache layer beyond ISR tag support
❌ Authentication helpers — forward your own headers
❌ Automatic pagination or cursor handling
❌ Magic or implicit behavior on import
❌ Configuration files (configure in code)

Focus on doing one thing well: idiomatic Next.js integration for @periodic/strontium.


🎨 TypeScript Support

Full TypeScript support with complete type safety:

import type {
  ServerClientOptions,
  EdgeClientOptions,
  RouteHandlerOptions,
} from '@periodic/strontium-next';

// createServerStrontiumClient is fully typed
const client = createServerStrontiumClient({ baseURL: '...' });

// wrapRouteHandler preserves the handler's type signature
export const GET = wrapRouteHandler(async (req: NextRequest) => {
  const res = await client.request<User[]>({ method: 'GET', url: '/users' });
  return NextResponse.json(res.data); // res.data typed as User[]
});

🧪 Testing

# Run tests
npm test

# Run tests with coverage
npm run test:coverage

# Run tests in watch mode
npm run test:watch

Note: All tests achieve >80% code coverage.


🤝 Related Packages

Part of the Periodic series by Uday Thakur:

Build complete, production-ready APIs with the Periodic series!


📖 Documentation


🛠️ Production Recommendations

Environment Variables

API_BASE_URL=https://api.example.com
NEXT_PUBLIC_API_URL=https://api.example.com
NODE_ENV=production

Log Aggregation

Pair with @periodic/iridium for structured JSON output:

import { createLogger, ConsoleTransport, JsonFormatter } from '@periodic/iridium';

const logger = createLogger({
  transports: [new ConsoleTransport({ formatter: new JsonFormatter() })],
});

const client = createServerStrontiumClient({ baseURL: process.env.API_BASE_URL! });

client.use({
  onAfterResponse: (ctx, res) => logger.info('http.response', { url: ctx.url, status: res.status }),
  onError: (ctx, err) => logger.error('http.error', { url: ctx.url, error: err.message }),
  onRetry: (ctx, err) => logger.warn('http.retry', { url: ctx.url, error: err.message }),
});

// Pipe to Elasticsearch, Datadog, CloudWatch, etc.

Error Monitoring

const client = createServerStrontiumClient({ baseURL: process.env.API_BASE_URL! });

client.use({
  onError: (ctx, err) => {
    Sentry.captureException(err, { extra: { url: ctx.url } });
  },
});

📝 License

MIT © Uday Thakur


🙏 Contributing

Contributions are welcome! Please read CONTRIBUTING.md for details on:

  • Code of conduct
  • Development setup
  • Pull request process
  • Coding standards
  • Architecture principles

📞 Support


🌟 Show Your Support

Give a ⭐️ if this project helped you build better applications!


Built with ❤️ by Uday Thakur for production-grade Node.js applications