npm package: @periodic/strontium-next
Description: Production-grade Next.js integration for @periodic/strontium — App Router, Pages Router, Edge, and Middleware all covered
Author: Uday Thakur
Latest version: 1.0.0

# 🔺 Periodic Strontium Next

[![npm version](https://img.shields.io/npm/v/@periodic/strontium-next.svg)](https://www.npmjs.com/package/@periodic/strontium-next)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.3-blue)](https://www.typescriptlang.org/)

**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 integration** — `taggedRequest` and `strontiumRevalidateTag` for Next.js ISR  
✅ **Hydration-safe singleton** — `getClientStrontiumClient` 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

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

Or with yarn:

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

---

## 🚀 Quick Start

```tsx
// 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**

```typescript
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:

```tsx
// 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:

```typescript
// 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:

```typescript
// 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:

```typescript
// 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:

```typescript
// 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:

```typescript
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:

```typescript
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

```tsx
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

```typescript
// 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

```tsx
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

```typescript
// 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

```typescript
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

```typescript
// 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

```typescript
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

```typescript
// 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

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

### Route Handlers

```typescript
wrapRouteHandler(handler, options?): RouteHandler
```

### Middleware

```typescript
createStrontiumMiddleware(options?): NextMiddleware
```

### Cache

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

### Types

```typescript
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 singleton** — `getClientStrontiumClient` 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:

```typescript
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

```bash
# 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:

- [**@periodic/strontium**](https://www.npmjs.com/package/@periodic/strontium) - The core HTTP client (required peer dependency)
- [**@periodic/strontium-react**](https://www.npmjs.com/package/@periodic/strontium-react) - React hooks integration
- [**@periodic/iridium**](https://www.npmjs.com/package/@periodic/iridium) - Structured logging
- [**@periodic/arsenic**](https://www.npmjs.com/package/@periodic/arsenic) - Semantic runtime monitoring
- [**@periodic/zirconium**](https://www.npmjs.com/package/@periodic/zirconium) - Environment configuration
- [**@periodic/vanadium**](https://www.npmjs.com/package/@periodic/vanadium) - Idempotency and distributed locks
- [**@periodic/obsidian**](https://www.npmjs.com/package/@periodic/obsidian) - HTTP error handling
- [**@periodic/titanium**](https://www.npmjs.com/package/@periodic/titanium) - Rate limiting
- [**@periodic/osmium**](https://www.npmjs.com/package/@periodic/osmium) - Redis caching

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

---

## 📖 Documentation

- [Quick Start Guide](QUICKSTART.md)
- [Contributing Guide](CONTRIBUTING.md)
- [Changelog](CHANGELOG.md)

---

## 🛠️ Production Recommendations

### Environment Variables

```bash
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:

```typescript
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

```typescript
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](LICENSE)

---

## 🙏 Contributing

Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on:

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

---

## 📞 Support

- 📧 **Email:** udaythakurwork@gmail.com
- 🐛 **Issues:** [GitHub Issues](https://github.com/udaythakur7469/periodic-strontium-next/issues)
- 💬 **Discussions:** [GitHub Discussions](https://github.com/udaythakur7469/periodic-strontium-next/discussions)

---

## 🌟 Show Your Support

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

---

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