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

@inso_web/els-next

v0.5.2

Published

Next.js helpers for @inso_web/els-client — logging in API routes (Pages + App Router), middleware.ts and edge runtime support. A drop-in replacement for console.log in Next.js apps.

Readme

@inso_web/els-next

npm version npm downloads TypeScript license MIT

Next.js helpers for the Inso Error Logs Service (ELS) — a managed SaaS for centralised event logging (debug → fatal) with AI-assisted error triage. Works in API Routes (App + Pages Router), middleware.ts, edge runtime, server actions, and the client bundle.

🇷🇺 Русская версия → README_RU.md


Table of contents


What you get

ELS ships with a built-in admin dashboard. Every event captured by this SDK — server (API routes, middleware, RSC) and client — lands there with full-text search, faceted filtering, AI-assisted diagnosis, and version-aware regression detection.

| | | |---|---| | Logs list | Event detail | | Virtual table with facet sidebar (app, env, version, source, level, browser, IP, category). Live mode auto-refreshes every 5s. | Full event metadata: timestamps, geo, env, app version, fingerprint, session, repetition cards, in-session correlation. | | AI diagnosis | Analytics | | Parsed stack trace + AI-assisted diagnosis: what broke, where, how to fix. | Timeline, donuts, top URLs/IPs, hourly heatmap, version-regression widget. |


Install

npm install @inso_web/els-client @inso_web/els-next

@inso_web/els-client is a peer dependency.

Requirements: Next.js 13+ (App Router) or Next.js 12+ (Pages Router), Node.js 18+.


Quick Start

0. Create the logger

lib/logger.ts — one module to import from everywhere:

import { createELSLogger } from '@inso_web/els-next';

export const log = createELSLogger({
  apiKey: process.env.NEXT_PUBLIC_ELS_API_KEY ?? '',
  appSlug: 'my-nextjs-app',
  serviceName: 'web',
  deploymentEnv: process.env.NODE_ENV === 'production' ? 'PRODUCTION' : 'DEV',
  appVersion: process.env.NEXT_PUBLIC_BUILD_VERSION,
  minLevel: 'info',
});

Don't have an API key yet? Sign up at lk.insoweb.ru — takes under a minute.

1. App Router (recommended)

app/api/users/[id]/route.ts:

import { log } from '@/lib/logger';

export async function GET(_req: Request, { params }: { params: { id: string } }) {
  log.info({ userId: params.id }, 'Fetching user');
  try {
    const user = await db.user.findUnique({ where: { id: params.id } });
    return Response.json(user);
  } catch (err) {
    log.error(err as Error, 'User fetch failed');
    return new Response('Internal error', { status: 500 });
  }
}

Global error boundary — app/global-error.tsx:

'use client';
import { useEffect } from 'react';
import { log } from '@/lib/logger';

export default function GlobalError({ error, reset }: { error: Error; reset: () => void }) {
  useEffect(() => { log.error(error, 'global-error boundary'); }, [error]);
  return (
    <html><body>
      <p>Something went wrong</p>
      <button onClick={() => reset()}>Try again</button>
    </body></html>
  );
}

2. Pages Router

pages/api/users/[id].ts:

import type { NextApiRequest, NextApiResponse } from 'next';
import { log } from '@/lib/logger';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  try {
    const user = await db.user.findUnique({ where: { id: String(req.query.id) } });
    res.json(user);
  } catch (err) {
    log.error(err as Error, 'User fetch failed');
    res.status(500).end('Internal error');
  }
}

Custom error page — pages/_error.tsx:

import { log } from '@/lib/logger';

function ErrorPage({ statusCode, err }: { statusCode: number; err?: Error }) {
  if (err) log.error(err, `_error ${statusCode}`);
  return <p>Status: {statusCode}</p>;
}
ErrorPage.getInitialProps = ({ res, err }: any) => ({
  statusCode: res?.statusCode ?? err?.statusCode ?? 404,
  err,
});
export default ErrorPage;

3. Client components

'use client';
import { log } from '@/lib/logger';

export function CheckoutButton() {
  const onClick = async () => {
    try {
      await fetch('/api/checkout', { method: 'POST' });
    } catch (err) {
      log.error(err as Error, 'Checkout failed');
    }
  };
  return <button onClick={onClick}>Pay</button>;
}

Global browser handlers — drop into app/layout.tsx or a small ErrorReporter component:

'use client';
import { useEffect } from 'react';
import { log } from '@/lib/logger';

export function ErrorReporter() {
  useEffect(() => {
    const onError = (e: ErrorEvent) => log.error(e.error ?? e.message);
    const onReject = (e: PromiseRejectionEvent) => log.error(e.reason);
    window.addEventListener('error', onError);
    window.addEventListener('unhandledrejection', onReject);
    return () => {
      window.removeEventListener('error', onError);
      window.removeEventListener('unhandledrejection', onReject);
    };
  }, []);
  return null;
}

4. Edge runtime

// middleware.ts
import { NextResponse, type NextRequest } from 'next/server';
import { log } from '@/lib/logger';

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

export function middleware(req: NextRequest) {
  log.info({ url: req.nextUrl.pathname }, 'Edge request');
  return NextResponse.next();
}

The logger uses only the global fetch — fully edge-compatible.


When to use what

| Scenario | Use | |---|---| | API route in App Router | import { log } + log.info(...), log.error(...) | | Server Component fetch failure | log.error(err, 'rsc-fetch') in try/catch | | Client component handler | 'use client' + same log | | Middleware / edge | Same log — only fetch is used | | Global render-crash | app/global-error.tsx boundary (App Router) | | Pages Router 4xx/5xx | pages/_error.tsx |

There is one logger surface across runtimes. Bundle splitter decides server vs client automatically — you don't.


Core concepts

One logger, two runtimes

createELSLogger(...) is safe to import from both server and client code. On the server it ships directly. In the browser it ships via fetch too — your apiKey is still an ELS scoped key (write-only for one app), so it's fine to expose in the bundle just like Sentry's public DSN.

Fire-and-forget

log.error(...) never throws and never blocks. Transport errors land in console.error (visible in server logs / browser devtools).

Bindings & child loggers

const reqLog = log.child({ requestId: crypto.randomUUID(), userId: '42' });
reqLog.info('processing checkout');

Use bindings to carry per-request context through async boundaries.


Configuration

ELSConfig matches the base client — see @inso_web/els-client. The most relevant fields:

| Option | Description | |---|---| | apiKey | API key (required) | | appSlug | App slug (required) | | serviceName | Module / service name | | deploymentEnv | DEV / STAGING / PRODUCTION | | appVersion | Version string (any format, ≤128 chars) | | minLevel | Minimum level to send |


Migration

From console.log + global error boundary

Before — server (Pages Router):

// pages/api/users/[id].ts
export default function handler(req, res) {
  try {
    // ...
  } catch (err) {
    console.error('user fetch failed', err);
    res.status(500).end();
  }
}

Before — client:

'use client';
useEffect(() => {
  const onError = (e: ErrorEvent) => console.error('window.error', e.error);
  window.addEventListener('error', onError);
  return () => window.removeEventListener('error', onError);
}, []);

After — single logger across both:

// lib/logger.ts
import { createELSLogger } from '@inso_web/els-next';
export const log = createELSLogger({ apiKey, appSlug: 'my-app' });
// pages/api/users/[id].ts
import { log } from '@/lib/logger';
export default function handler(req, res) {
  try { /* ... */ }
  catch (err) {
    log.error(err as Error, 'user fetch failed');
    res.status(500).end();
  }
}
'use client';
import { log } from '@/lib/logger';
useEffect(() => {
  const onError = (e: ErrorEvent) => log.error(e.error ?? e.message);
  window.addEventListener('error', onError);
  return () => window.removeEventListener('error', onError);
}, []);

| console method | ELS method | Notes | |---|---|---| | console.log / info | log.info | | | console.warn | log.warn | | | console.error | log.error | First arg can be Error or string |

Gotchas:

  • console.* keeps writing to stdout / devtools. Leave it for local dev; ELS only carries remote-worthy events.
  • Variadic printf-style formatting is not supported — pass structured fields instead.

From @sentry/nextjs

Before:

// sentry.server.config.ts
import * as Sentry from '@sentry/nextjs';
Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV,
  release: process.env.NEXT_PUBLIC_BUILD_VERSION,
});

// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';
Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  environment: process.env.NODE_ENV,
});

// api/users/[id]/route.ts
export async function GET() {
  try { /* ... */ }
  catch (err) {
    Sentry.captureException(err);
    return new Response('500', { status: 500 });
  }
}

After:

// lib/logger.ts — single config for client + server
import { createELSLogger } from '@inso_web/els-next';
export const log = createELSLogger({
  apiKey: process.env.NEXT_PUBLIC_ELS_API_KEY!,
  appSlug: 'my-app',
  deploymentEnv: process.env.NODE_ENV === 'production' ? 'PRODUCTION' : 'DEV',
  appVersion: process.env.NEXT_PUBLIC_BUILD_VERSION,
});

// api/users/[id]/route.ts
import { log } from '@/lib/logger';
export async function GET() {
  try { /* ... */ }
  catch (err) {
    log.error(err as Error, 'user fetch failed');
    return new Response('500', { status: 500 });
  }
}

| Sentry concept | ELS equivalent | Notes | |---|---|---| | sentry.{server,client,edge}.config.ts | One lib/logger.ts | No runtime-specific configs | | dsn | apiKey + appSlug | Three explicit fields | | captureException(err) | log.error(err) | | | captureMessage(msg, level) | log.<level>(msg) | | | release | appVersion | Same field, any string ≤128 chars | | environment | deploymentEnv | Fixed enum | | Source maps upload | Not provided | Pair with another tool if critical | | withSentryConfig(...) wrapper | Not needed | No build-time wrapper |

Gotchas:

  • Sentry's Next plugin instruments build artefacts (sourcemap upload, tunnel route). ELS does neither — drop withSentryConfig and remove sentry-cli.
  • For App Router's automatic error boundaries Sentry registers an integration; ELS uses the standard app/global-error.tsx pattern (shown above).
  • Tracing / performance is out of scope — keep Sentry Performance alongside if you depend on it.

Versioning

Next.js inlines NEXT_PUBLIC_* variables at build time. Pass through Dockerfile:

ARG NEXT_PUBLIC_BUILD_VERSION=dev
ENV NEXT_PUBLIC_BUILD_VERSION=$NEXT_PUBLIC_BUILD_VERSION
RUN npm run build
# .gitlab-ci.yml
- export BUILD_VERSION=$(date -u +%Y%m%d%H%M%S)
- docker build --build-arg NEXT_PUBLIC_BUILD_VERSION="$BUILD_VERSION" ...
createELSLogger({ ..., appVersion: process.env.NEXT_PUBLIC_BUILD_VERSION });

ELS accepts any string ≤128 chars: semver, CalVer, date-compact, git SHA, opaque. The server auto-detects the format and sorts timelines.


Quick reference

| Need | Use | |---|---| | API route logging | import { log } + log.info(...) | | Server Component error | log.error(err) inside try/catch | | Client error reporter | useEffect with window.error handler | | Edge middleware | Same log (only fetch used) | | Global render-crash | app/global-error.tsx | | Pages-Router 4xx/5xx | pages/_error.tsx | | Hide key from client bundle | Use server-only env vars + an internal /api/log proxy | | Suppress noisy levels | minLevel: 'warn' |


Why ELS

ELS for Node.js is a focused logging SaaS, not a full observability suite. It optimises for capture speed, AI-driven triage, and a low integration cost.

  • Lower weight. No transitive deps, no build-time plugins.
  • Zero external API calls. Only POST /errors[/batch] and GET /health.
  • AI-assisted diagnosis on every stack trace.
  • 5-minute integration. One lib/logger.ts covers server, client, and edge.
  • Predictable price. Tariffs live in the dashboard.

Detailed comparison

| Category | ELS | Sentry | Datadog / New Relic | Grafana Loki | LogRocket / Logtail / BetterStack | |---|---|---|---|---|---| | Hosting model | Managed SaaS | SaaS or self-hosted | SaaS only | Self-hosted / Grafana Cloud | SaaS | | SDK runtime deps | Zero | Medium (sub-SDKs, integrations) | Heavy (agent + tracing) | Promtail / agent | Medium | | Typical integration time | ~5 min | 10–20 min | 30–60 min | Hours to days | 10–20 min | | AI-assisted triage | Built-in | Paid add-on | Paid add-on | None | None | | Error grouping / fingerprint | Yes | Yes | Yes | Manual via LogQL | Partial | | Source-map upload | No | Yes | Yes | n/a | Partial | | Session replay (frontend) | No | Paid | Paid | n/a | Yes (core) | | Distributed tracing / APM | No | Partial | Yes (core) | Yes with Tempo | No | | Infrastructure metrics | No | No | Yes (core) | Yes with Mimir | No | | Free tier log retention | 24 hours | 30 days (limited volume) | Trial only | Self-cost | 3–30 days | | Russian-language support / docs | Native | Community | Limited | Community | None |

When ELS is the wrong choice

  • You need a single vendor for APM + logs + metrics under one bill — go Datadog or New Relic.
  • Your frontend bug triage relies on DOM session replay — go LogRocket or Sentry Replay.
  • You ship a public mobile app and need crash symbolication + ANR detection — Firebase Crashlytics or Sentry Mobile.

For everything else — backend errors, frontend JS errors, request logs, structured app events with version-aware analytics — ELS is built to be the cheapest path to a working dashboard.

Sign up at lk.insoweb.ru to grab an API key.


API

function createELSLogger(config: ELSConfig): Logger;

ELSConfig matches the base client — see @inso_web/els-client. Logger methods: fatal / error / warn / info / debug / trace / child / flush.


FAQ

Is the API key safe in the client bundle? Yes. ELS keys are scoped — a write key for your app cannot read events. The same model Sentry uses for public DSNs. If you still want to hide it, run an internal /api/log proxy and only log from API routes.

What if the API key is empty? The SDK does not throw — it returns a no-op logger. Useful for preview deployments without keys yet.

Does it work in edge runtimes? Yes. The transport is plain fetch — no Node APIs.

Does withSentryConfig need replacement? No build-time wrapper is needed. Just import and use.


Other ELS SDKs

Same wire format, same dashboard — pick by stack.

Node.js family

Other stacks


Pricing

Free tier — 24-hour log retention. See lk.insoweb.ru for the full tariff matrix.


License

MIT © INSOWEB