@watchupltd/next
v1.0.3
Published
Next.js integration for incident tracking with support for both App Router and Pages Router, client-side and server-side error tracking.
Downloads
31
Readme
@watchupltd/next
Next.js integration for incident tracking with support for both App Router and Pages Router, client-side and server-side error tracking.
Installation
npm install @watchupltd/nextQuick Start
App Router (Next.js 13+)
Client-side Setup
// app/layout.tsx
import { IncidentProvider } from '@watchupltd/next/client';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<IncidentProvider
apiKey={process.env.NEXT_PUBLIC_INCIDENT_API_KEY}
projectId={process.env.NEXT_PUBLIC_INCIDENT_PROJECT_ID}
environment="production"
>
{children}
</IncidentProvider>
</body>
</html>
);
}Use in Client Components
'use client';
import { useIncident } from '@watchupltd/next/client';
export default function Page() {
const { captureError, captureMessage } = useIncident();
const handleClick = async () => {
try {
await fetchData();
} catch (error) {
captureError(error);
}
};
return <button onClick={handleClick}>Click me</button>;
}Server-side Setup
// app/api/route.ts or any server component
import { initServerSide, captureServerError } from '@watchupltd/next/server';
// Initialize once (e.g., in a middleware or at app startup)
initServerSide({
apiKey: process.env.INCIDENT_API_KEY,
projectId: process.env.INCIDENT_PROJECT_ID,
environment: 'production'
});
export async function GET() {
try {
const data = await fetchData();
return Response.json(data);
} catch (error) {
captureServerError(error, {
tags: { route: '/api/data' }
});
return Response.json({ error: 'Internal error' }, { status: 500 });
}
}Pages Router (Legacy)
Client-side
// pages/_app.tsx
import { IncidentProvider } from '@watchupltd/next/client';
export default function App({ Component, pageProps }) {
return (
<IncidentProvider
apiKey={process.env.NEXT_PUBLIC_INCIDENT_API_KEY}
projectId={process.env.NEXT_PUBLIC_INCIDENT_PROJECT_ID}
>
<Component {...pageProps} />
</IncidentProvider>
);
}Server-side (getServerSideProps)
import { withIncidentServerSideProps } from '@watchupltd/next/server';
export const getServerSideProps = withIncidentServerSideProps(async (context) => {
const data = await fetchData();
return { props: { data } };
});API Routes
// pages/api/users.ts
import { withIncidentAPI } from '@watchupltd/next/server';
export default withIncidentAPI(async (req, res) => {
const users = await getUsers();
res.json(users);
});What It Tracks
Client-side
- React component errors (ErrorBoundary)
- Global JavaScript errors
- Unhandled promise rejections
- Console errors (optional)
- Manual captures via hooks
Server-side
- SSR errors in
getServerSideProps - API route errors
- App Router server component errors
- Middleware errors
- Manual captures via
captureServerError()
Configuration
Environment Variables
# Client-side (must be prefixed with NEXT_PUBLIC_)
NEXT_PUBLIC_INCIDENT_API_KEY=your-api-key
NEXT_PUBLIC_INCIDENT_PROJECT_ID=your-project-id
# Server-side (no prefix needed)
INCIDENT_API_KEY=your-api-key
INCIDENT_PROJECT_ID=your-project-idnext.config.js
Add transpilation for the SDK packages:
/** @type {import('next').NextConfig} */
const nextConfig = {
transpilePackages: ['@watchupltd/next', '@watchupltd/react', '@watchupltd/core']
};
module.exports = nextConfig;Client-side API
<IncidentProvider>
Same as @watchupltd/react. See React SDK docs for full API.
useIncident()
const {
captureError,
captureMessage,
addBreadcrumb,
setUser,
setTag,
setTags
} = useIncident();Server-side API
initServerSide(config: IncidentConfig): IncidentClient
Initialize the server-side client. Call once at app startup.
import { initServerSide } from '@watchupltd/next/server';
initServerSide({
apiKey: process.env.INCIDENT_API_KEY,
projectId: process.env.INCIDENT_PROJECT_ID,
environment: 'production'
});captureServerError(error: Error, context?: any): void
Capture an error on the server.
import { captureServerError } from '@watchupltd/next/server';
try {
await dangerousOperation();
} catch (error) {
captureServerError(error, {
tags: { operation: 'database' },
extra: { query: 'SELECT * FROM users' }
});
}withIncidentServerSideProps(handler)
Wrap getServerSideProps to automatically capture errors.
import { withIncidentServerSideProps } from '@watchupltd/next/server';
export const getServerSideProps = withIncidentServerSideProps(async (context) => {
// Your logic here
return { props: {} };
});withIncidentAPI(handler)
Wrap API route handlers to automatically capture errors.
import { withIncidentAPI } from '@watchupltd/next/server';
export default withIncidentAPI(async (req, res) => {
// Your API logic here
res.json({ success: true });
});getServerClient(): IncidentClient | null
Get the server-side client instance.
import { getServerClient } from '@watchupltd/next/server';
const client = getServerClient();
if (client) {
client.captureMessage('Server started', 'info');
}Examples
Track Server Component Errors
// app/users/page.tsx
import { captureServerError } from '@watchupltd/next/server';
export default async function UsersPage() {
try {
const users = await fetchUsers();
return <UserList users={users} />;
} catch (error) {
captureServerError(error, {
tags: { page: 'users' }
});
return <ErrorMessage />;
}
}Track Middleware Errors
// middleware.ts
import { NextResponse } from 'next/server';
import { captureServerError } from '@watchupltd/next/server';
export function middleware(request) {
try {
// Your middleware logic
return NextResponse.next();
} catch (error) {
captureServerError(error, {
tags: { middleware: 'auth' },
extra: { path: request.nextUrl.pathname }
});
return NextResponse.redirect('/error');
}
}Track Form Submissions
'use client';
import { useIncident } from '@watchupltd/next/client';
export default function ContactForm() {
const { captureError, addBreadcrumb } = useIncident();
const handleSubmit = async (e) => {
e.preventDefault();
addBreadcrumb({
message: 'User submitted contact form',
category: 'form'
});
try {
await submitForm(formData);
} catch (error) {
captureError(error, {
tags: { form: 'contact' }
});
}
};
return <form onSubmit={handleSubmit}>...</form>;
}SSR Safety
The SDK is fully SSR-safe:
- Client-side code only runs in the browser
- Server-side code only runs on the server
- No
windowor browser API usage during SSR - Separate client/server entry points
TypeScript Support
Full TypeScript support with type definitions included.
import type { IncidentConfig, SeverityLevel } from '@watchupltd/next/server';Data Models
Event Model
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"project_id": "proj_abc123",
"fingerprint": "a1b2c3d4e5f6...",
"title": "Error",
"message": "Something went wrong",
"stack_trace": "Error: Something went wrong\n at Component (page.tsx:10:5)",
"service": "frontend",
"environment": "production",
"severity": "error",
"timestamp": 1678901234567,
"platform": "javascript",
"release": "1.2.3",
"metadata": {
"nextjs": {
"router": "app",
"pathname": "/users"
}
},
"user": {
"id": "user-123",
"email": "[email protected]",
"username": "john_doe"
},
"tags": {
"component": "UsersPage",
"version": "1.2.3"
},
"breadcrumbs": [
{
"timestamp": 1678901230000,
"message": "Page loaded",
"category": "navigation",
"level": "info",
"data": {"pathname": "/users"}
}
],
"context": {
"nextjs": {
"version": "14.0.0"
}
}
}User Model
{
"id": "user-123",
"email": "[email protected]",
"username": "john_doe",
"ip_address": "192.168.1.1"
}Breadcrumb Model
{
"timestamp": 1678901234567,
"message": "User action description",
"category": "user-action | navigation | http | database | console",
"level": "fatal | error | warning | info | debug",
"data": {
"key": "value"
}
}License
MIT
