Skip to main content

Next.js Monitoring

When deploying Next.js applications to production, ensuring they run smoothly is crucial. Monitoring helps you track application performance, detect issues before users do, and understand how your application is being used. In this guide, we'll explore how to set up effective monitoring for your Next.js applications.

Why Monitor Your Next.js Application?

Monitoring your Next.js application provides several benefits:

  • Early Issue Detection: Identify bugs, performance issues, and errors before they impact a large number of users
  • Performance Insights: Understand which parts of your application might need optimization
  • User Behavior Analysis: Learn how users interact with your application
  • Server Health Tracking: Monitor server resources, response times, and availability

Key Metrics to Monitor in Next.js Applications

1. Web Vitals

Web Vitals are quality signals that measure a user's experience on your website:

  • Largest Contentful Paint (LCP): Measures loading performance
  • First Input Delay (FID): Measures interactivity
  • Cumulative Layout Shift (CLS): Measures visual stability
  • First Contentful Paint (FCP): When first content is rendered on the page

Next.js provides built-in Web Vitals reporting through its reportWebVitals function.

2. Server-Side Metrics

  • Memory Usage: Track how much memory your application consumes
  • CPU Utilization: Monitor CPU usage to prevent overloading
  • Response Times: Measure how quickly your server responds to requests
  • Error Rates: Track the frequency of server errors

3. API Performance

  • API Response Times: How long your API endpoints take to respond
  • API Failure Rates: Percentage of API requests that fail
  • API Usage: Which endpoints are called most frequently

Implementing Monitoring in Next.js

Basic Monitoring with reportWebVitals

Next.js includes a reportWebVitals function that allows you to track Web Vitals metrics. Here's how to implement it:

  1. Open or create your _app.js file in the pages directory:
jsx
// pages/_app.js
import '../styles/globals.css';

export function reportWebVitals(metric) {
console.log(metric);

// You can send the metrics to your analytics service
// Example: sendToAnalytics(metric);
}

function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}

export default MyApp;
  1. Create a function to send these metrics to your analytics service:
jsx
// Example function to send metrics to an analytics service
function sendToAnalytics(metric) {
const { id, name, value } = metric;

// Example using fetch to send to an analytics endpoint
fetch('/api/analytics', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
id,
name,
value,
timestamp: Date.now(),
}),
});
}

Monitoring with Third-Party Services

1. Setting up Application Monitoring with Sentry

Sentry is a popular error tracking and monitoring service that works well with Next.js.

  1. Install the required packages:
bash
npm install @sentry/nextjs
  1. Initialize Sentry in your Next.js project:
bash
npx @sentry/wizard -i nextjs

This will create the necessary configuration files for Sentry.

  1. The wizard creates multiple files, including sentry.client.config.js:
jsx
// sentry.client.config.js
import * as Sentry from '@sentry/nextjs';

Sentry.init({
dsn: "YOUR_SENTRY_DSN", // Replace with your actual DSN
tracesSampleRate: 1.0, // Adjust in production for performance
// Additional configuration options
});
  1. Example error handling in a component:
jsx
// pages/example.js
import { useState } from 'react';
import * as Sentry from '@sentry/nextjs';

export default function ExamplePage() {
const [error, setError] = useState(false);

const handleClick = () => {
try {
// Simulate an error
throw new Error('Example error');
} catch (error) {
Sentry.captureException(error);
setError(true);
}
};

return (
<div>
<h1>Sentry Example</h1>
<button onClick={handleClick}>Generate Error</button>
{error && <p>Error captured and sent to Sentry!</p>}
</div>
);
}

2. Performance Monitoring with New Relic

New Relic offers comprehensive application performance monitoring.

  1. Install New Relic's Node.js agent:
bash
npm install newrelic
  1. Create a newrelic.js configuration file in your project's root directory:
js
// newrelic.js
'use strict';

exports.config = {
app_name: ['Your Next.js App'],
license_key: 'YOUR_LICENSE_KEY',
logging: {
level: 'info'
},
allow_all_headers: true,
attributes: {
exclude: [
'request.headers.cookie',
'request.headers.authorization',
'request.headers.proxyAuthorization',
'request.headers.setCookie*',
'request.headers.x*',
'response.headers.cookie',
'response.headers.authorization',
'response.headers.proxyAuthorization',
'response.headers.setCookie*',
'response.headers.x*'
]
}
};
  1. Create a custom server for Next.js to integrate New Relic:
js
// server.js
require('newrelic');
const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

app.prepare().then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true);
handle(req, res, parsedUrl);
}).listen(3000, (err) => {
if (err) throw err;
console.log('> Ready on http://localhost:3000');
});
});
  1. Update your package.json to use this custom server:
json
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
}

Creating a Custom Monitoring Dashboard

You might want to create a custom dashboard to monitor your Next.js application. Here's how to implement a simple monitoring page:

  1. Create an API route to collect metrics:
jsx
// pages/api/metrics.js
let metrics = {
pageViews: 0,
errors: 0,
apiCalls: 0,
avgResponseTime: 0,
};

export default function handler(req, res) {
if (req.method === 'POST') {
// Update metrics based on incoming data
const data = req.body;

if (data.type === 'pageView') {
metrics.pageViews += 1;
} else if (data.type === 'error') {
metrics.errors += 1;
} else if (data.type === 'apiCall') {
metrics.apiCalls += 1;

// Update average response time
const newTotal = metrics.avgResponseTime * (metrics.apiCalls - 1) + data.responseTime;
metrics.avgResponseTime = newTotal / metrics.apiCalls;
}

res.status(200).json({ success: true });
} else if (req.method === 'GET') {
// Return current metrics
res.status(200).json(metrics);
} else {
res.status(405).end(); // Method not allowed
}
}
  1. Create a dashboard page to display metrics:
jsx
// pages/admin/dashboard.js
import { useState, useEffect } from 'react';

export default function Dashboard() {
const [metrics, setMetrics] = useState({
pageViews: 0,
errors: 0,
apiCalls: 0,
avgResponseTime: 0,
});

const [loading, setLoading] = useState(true);

useEffect(() => {
// Fetch metrics every 5 seconds
const fetchMetrics = async () => {
try {
const response = await fetch('/api/metrics');
const data = await response.json();
setMetrics(data);
setLoading(false);
} catch (error) {
console.error('Error fetching metrics:', error);
}
};

fetchMetrics();

const interval = setInterval(fetchMetrics, 5000);
return () => clearInterval(interval);
}, []);

if (loading) {
return <div>Loading metrics...</div>;
}

return (
<div className="dashboard">
<h1>Application Monitoring Dashboard</h1>

<div className="metrics-grid">
<div className="metric-card">
<h3>Page Views</h3>
<p className="metric-value">{metrics.pageViews}</p>
</div>

<div className="metric-card">
<h3>Errors</h3>
<p className="metric-value">{metrics.errors}</p>
</div>

<div className="metric-card">
<h3>API Calls</h3>
<p className="metric-value">{metrics.apiCalls}</p>
</div>

<div className="metric-card">
<h3>Avg Response Time</h3>
<p className="metric-value">{metrics.avgResponseTime.toFixed(2)}ms</p>
</div>
</div>
</div>
);
}
  1. Add some basic styles for the dashboard:
css
/* Add to your CSS files */
.dashboard {
padding: 20px;
}

.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-top: 20px;
}

.metric-card {
background: #f5f5f5;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.metric-value {
font-size: 32px;
font-weight: bold;
color: #0070f3;
}

Log Management for Next.js Applications

Logs are essential for debugging and monitoring. Here's how to implement effective logging in Next.js:

1. Server-side Logging

For server-side logs, you can use a package like Winston or Pino:

bash
npm install winston

Create a logger module:

jsx
// lib/logger.js
import winston from 'winston';

const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.Console(),
// Add other transports like file storage or remote logging services
],
});

export default logger;

Use the logger in your API routes:

jsx
// pages/api/example.js
import logger from '../../lib/logger';

export default function handler(req, res) {
logger.info('API route called', {
path: req.url,
method: req.method,
query: req.query,
});

try {
// Your API logic here
res.status(200).json({ success: true });
} catch (error) {
logger.error('API error occurred', {
error: error.message,
stack: error.stack,
});
res.status(500).json({ error: 'Internal server error' });
}
}

2. Client-side Logging

For client-side logging, you can send logs to your server:

jsx
// utils/clientLogger.js
export const logLevel = {
INFO: 'info',
WARN: 'warn',
ERROR: 'error',
};

export async function logToServer(message, level = logLevel.INFO, metadata = {}) {
try {
await fetch('/api/logs', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message,
level,
metadata,
timestamp: new Date().toISOString(),
}),
});
} catch (error) {
console.error('Failed to send log to server:', error);
}
}

Create an API endpoint to receive these logs:

jsx
// pages/api/logs.js
import logger from '../../lib/logger';

export default function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).end();
}

const { message, level, metadata, timestamp } = req.body;

// Log with the appropriate level
logger[level](message, {
...metadata,
timestamp,
source: 'client',
});

res.status(200).json({ success: true });
}

Monitoring Next.js in Production

For production environments, consider these additional monitoring strategies:

1. Health Check Endpoint

Create a dedicated health check endpoint for your monitoring tools:

jsx
// pages/api/health.js
export default function handler(req, res) {
// Check database connection
const dbHealthy = checkDatabaseConnection();

// Check external services
const servicesHealthy = checkExternalServices();

if (dbHealthy && servicesHealthy) {
res.status(200).json({
status: 'healthy',
uptime: process.uptime(),
timestamp: Date.now()
});
} else {
res.status(503).json({
status: 'unhealthy',
dbHealthy,
servicesHealthy,
timestamp: Date.now()
});
}
}

// Example health check functions
function checkDatabaseConnection() {
// Implementation depends on your database
return true;
}

function checkExternalServices() {
// Check any external services your app depends on
return true;
}

2. Automated Alerts

Set up alerts based on your monitoring metrics to notify you of issues. For example, with Sentry:

jsx
// sentry.client.config.js
import * as Sentry from '@sentry/nextjs';

Sentry.init({
dsn: "YOUR_SENTRY_DSN",
tracesSampleRate: 1.0,
beforeSend(event) {
// Example: Custom logic for critical errors
if (event.level === 'fatal') {
// You could trigger additional notifications here
notifyCriticalError(event);
}
return event;
}
});

function notifyCriticalError(event) {
// Send SMS, call webhook, etc.
console.log('CRITICAL ERROR:', event);
}

Best Practices for Next.js Monitoring

  1. Monitor Both Client and Server: Next.js has both client and server components, monitor both sides
  2. Focus on User Experience Metrics: Prioritize metrics that directly impact user experience
  3. Set Up Meaningful Alerts: Don't alert on everything, focus on what truly matters
  4. Regular Performance Audits: Schedule regular audits of your monitoring data to spot trends
  5. Keep Monitoring Overhead Low: Ensure your monitoring tools don't significantly impact performance
  6. Version Your Deployments: Tag your monitoring data with deployment versions to correlate issues with changes
  7. Monitor API Endpoints: Track the performance and error rates of your API endpoints

Summary

Monitoring is essential for maintaining reliable Next.js applications in production. This guide covered:

  • Setting up basic Web Vitals monitoring using Next.js's built-in capabilities
  • Implementing error tracking with Sentry
  • Performance monitoring with New Relic
  • Creating a custom monitoring dashboard
  • Implementing effective logging strategies
  • Production monitoring considerations and best practices

By implementing these monitoring strategies, you'll gain valuable insights into your Next.js application's performance and user experience, allowing you to address issues proactively and make data-driven optimization decisions.

Additional Resources

Exercises

  1. Set up basic Web Vitals reporting in your Next.js application and log the results.
  2. Implement Sentry error tracking in a Next.js project and create a test error to verify it works.
  3. Create a simple monitoring dashboard that displays page load times for different routes in your application.
  4. Set up a health check endpoint that verifies all the services your application depends on.
  5. Implement client-side logging that sends errors to your server for centralized logging.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)