W produkcyjnych aplikacjach Next.js, monitoring wydajności i śledzenie błędów są kluczowe dla utrzymania wysokiej jakości dostarczanych usług. W tym module poznamy narzędzia i techniki, które pozwolą skutecznie monitorować aplikację, wykrywać problemy przed użytkownikami i optymalizować wydajność na bieżąco.
Monitoring to proces ciągłego obserwowania aplikacji w celu:
Sentry to jedno z najpopularniejszych narzędzi do monitoringu błędów w aplikacjach JavaScript i Next.js.
1npm install @sentry/nextjs
2# lub
3yarn add @sentry/nextjsUtwórz pliki konfiguracyjne:
1// sentry.client.config.js
2import * as Sentry from '@sentry/nextjs';
3
4Sentry.init({
5 dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
6
7 // Ustawienia środowiska
8 environment: process.env.NODE_ENV,
9
10 // Sample rate dla transakcji wydajnościowych
11 tracesSampleRate: 1.0,
12
13 // Włącz Replay dla sesji użytkowników
14 replaysSessionSampleRate: 0.1, // 10% sesji
15 replaysOnErrorSampleRate: 1.0, // 100% sesji z błędami
16
17 // Integracje
18 integrations: [
19 new Sentry.Replay({
20 maskAllText: false,
21 blockAllMedia: false,
22 }),
23 ],
24
25 // Filtrowanie błędów
26 beforeSend(event) {
27 // Ignoruj pewne typy błędów
28 if (event.exception) {
29 const error = event.exception.values[0];
30 if (error.type === 'ChunkLoadError') {
31 return null; // Ignoruj błędy ładowania chunków
32 }
33 }
34 return event;
35 },
36});1// sentry.server.config.js
2import * as Sentry from '@sentry/nextjs';
3
4Sentry.init({
5 dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
6 environment: process.env.NODE_ENV,
7 tracesSampleRate: 1.0,
8
9 // Dodatkowe opcje dla serwera
10 debug: false,
11
12 // Integracje specyficzne dla Node.js
13 integrations: [
14 new Sentry.Integrations.Http({ tracing: true }),
15 ],
16});1// sentry.edge.config.js
2import * as Sentry from '@sentry/nextjs';
3
4Sentry.init({
5 dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
6 environment: process.env.NODE_ENV,
7 tracesSampleRate: 1.0,
8 debug: false,
9});1// next.config.js
2const { withSentryConfig } = require('@sentry/nextjs');
3
4const nextConfig = {
5 // Twoja standardowa konfiguracja Next.js
6};
7
8const sentryWebpackPluginOptions = {
9 // Dodatkowe opcje konfiguracyjne dla Sentry webpack plugin
10 silent: true, // Wycisz logi Sentry podczas budowania
11 org: process.env.SENTRY_ORG,
12 project: process.env.SENTRY_PROJECT,
13};
14
15module.exports = withSentryConfig(nextConfig, sentryWebpackPluginOptions);1// lib/sentry.ts
2import * as Sentry from '@sentry/nextjs';
3
4// Funkcja do dodawania kontekstu użytkownika
5export function setUserContext(user: { id: string; email: string; name?: string }) {
6 Sentry.setUser({
7 id: user.id,
8 email: user.email,
9 username: user.name,
10 });
11}
12
13// Funkcja do dodawania tagów
14export function addTags(tags: Record<string, string>) {
15 Sentry.setTags(tags);
16}
17
18// Funkcja do manualnego raportowania błędów
19export function captureException(error: Error, context?: Record<string, any>) {
20 Sentry.withScope((scope) => {
21 if (context) {
22 scope.setContext('additional_info', context);
23 }
24 Sentry.captureException(error);
25 });
26}
27
28// Funkcja do śledzenia wydajności
29export function startTransaction(name: string, op: string) {
30 return Sentry.startTransaction({ name, op });
31}1// components/ErrorBoundary.tsx
2'use client';
3
4import React from 'react';
5import * as Sentry from '@sentry/nextjs';
6
7interface ErrorBoundaryState {
8 hasError: boolean;
9 eventId?: string;
10}
11
12class ErrorBoundary extends React.Component<
13 React.PropsWithChildren<{}>,
14 ErrorBoundaryState
15> {
16 constructor(props: React.PropsWithChildren<{}>) {
17 super(props);
18 this.state = { hasError: false };
19 }
20
21 static getDerivedStateFromError(_: Error): ErrorBoundaryState {
22 return { hasError: true };
23 }
24
25 componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
26 const eventId = Sentry.captureException(error, {
27 contexts: {
28 react: {
29 componentStack: errorInfo.componentStack,
30 },
31 },
32 });
33
34 this.setState({ eventId });
35 }
36
37 render() {
38 if (this.state.hasError) {
39 return (
40 <div className="error-fallback">
41 <h2>Ups! Coś poszło nie tak</h2>
42 <p>Wystąpił nieoczekiwany błąd. Nasz zespół został powiadomiony.</p>
43 <details style={{ whiteSpace: 'pre-wrap' }}>
44 ID błędu: {this.state.eventId}
45 </details>
46 <button onClick={() => window.location.reload()}>
47 Odśwież stronę
48 </button>
49 </div>
50 );
51 }
52
53 return this.props.children;
54 }
55}
56
57export default ErrorBoundary;Core Web Vitals to kluczowe metryki wydajności, które Google używa do oceny doswiadczenia użytkownika:
1// lib/web-vitals.ts
2import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
3import * as Sentry from '@sentry/nextjs';
4
5interface WebVitalMetric {
6 name: string;
7 value: number;
8 id: string;
9 delta: number;
10}
11
12// Funkcja do raportowania metryk
13function sendToAnalytics(metric: WebVitalMetric) {
14 // Wyślij do Google Analytics
15 if (typeof window !== 'undefined' && window.gtag) {
16 window.gtag('event', metric.name, {
17 event_category: 'Web Vitals',
18 event_label: metric.id,
19 value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value),
20 non_interaction: true,
21 });
22 }
23
24 // Wyślij do Sentry
25 Sentry.addBreadcrumb({
26 category: 'web-vital',
27 message: `${metric.name}: ${metric.value}`,
28 level: 'info',
29 data: {
30 name: metric.name,
31 value: metric.value,
32 id: metric.id,
33 delta: metric.delta,
34 },
35 });
36
37 // W przypadku słabych wyników, wyślij jako event do Sentry
38 const thresholds = {
39 LCP: 2500,
40 FID: 100,
41 CLS: 0.1,
42 };
43
44 if (metric.value > thresholds[metric.name as keyof typeof thresholds]) {
45 Sentry.captureMessage(`Poor ${metric.name}: ${metric.value}`, 'warning');
46 }
47}
48
49// Zainicjuj pomiary Web Vitals
50export function initWebVitals() {
51 getCLS(sendToAnalytics);
52 getFID(sendToAnalytics);
53 getFCP(sendToAnalytics);
54 getLCP(sendToAnalytics);
55 getTTFB(sendToAnalytics);
56}1// components/WebVitalsReporter.tsx
2'use client';
3
4import { useEffect } from 'react';
5import { useReportWebVitals } from 'next/web-vitals';
6
7export default function WebVitalsReporter() {
8 useReportWebVitals((metric) => {
9 // Wyślij do różnych systemów analitycznych
10 switch (metric.name) {
11 case 'FCP':
12 case 'LCP':
13 case 'CLS':
14 case 'FID':
15 case 'TTFB':
16 // Wyślij do własnego API
17 fetch('/api/analytics/web-vitals', {
18 method: 'POST',
19 headers: { 'Content-Type': 'application/json' },
20 body: JSON.stringify(metric),
21 }).catch(console.error);
22 break;
23 default:
24 break;
25 }
26 });
27
28 return null;
29}1// app/api/analytics/web-vitals/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3import { prisma } from '@/lib/prisma'; // Założmy, że używasz Prisma
4
5export async function POST(request: NextRequest) {
6 try {
7 const metric = await request.json();
8
9 // Walidacja podstawowa
10 if (!metric.name || !metric.value) {
11 return NextResponse.json({ error: 'Invalid metric data' }, { status: 400 });
12 }
13
14 // Zapisz do bazy danych
15 await prisma.webVitalMetric.create({
16 data: {
17 name: metric.name,
18 value: metric.value,
19 id: metric.id,
20 delta: metric.delta,
21 url: request.headers.get('referer') || '',
22 userAgent: request.headers.get('user-agent') || '',
23 timestamp: new Date(),
24 },
25 });
26
27 return NextResponse.json({ success: true });
28 } catch (error) {
29 console.error('Error saving web vital metric:', error);
30 return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
31 }
32}New Relic oferuje kompleksowe rozwiązanie do monitoringu wydajności aplikacji Next.js.
1npm install newrelic1// newrelic.js (w root projektu)
2'use strict';
3
4exports.config = {
5 app_name: ['Next.js App'],
6 license_key: process.env.NEW_RELIC_LICENSE_KEY,
7
8 // Konfiguracja logowania
9 logging: {
10 level: 'info',
11 },
12
13 // Włącz distributed tracing
14 distributed_tracing: {
15 enabled: true,
16 },
17
18 // Monitoring browserów
19 browser_monitoring: {
20 enable: true,
21 },
22
23 // Konfiguracja specyficzna dla Next.js
24 allow_all_headers: true,
25 attributes: {
26 exclude: [
27 'request.headers.cookie',
28 'request.headers.authorization',
29 'request.headers.x-*',
30 ],
31 },
32};1// next.config.js
2if (process.env.NODE_ENV === 'production') {
3 require('./newrelic');
4}
5
6const nextConfig = {
7 // Twoja konfiguracja Next.js
8};
9
10module.exports = nextConfig;1// lib/monitoring.ts
2import newrelic from 'newrelic';
3
4// Funkcja do śledzenia niestandardowych metryk
5export function recordCustomMetric(name: string, value: number) {
6 if (process.env.NODE_ENV === 'production') {
7 newrelic.recordMetric(name, value);
8 }
9}
10
11// Funkcja do śledzenia eventów biznesowych
12export function recordCustomEvent(eventType: string, attributes: Record<string, any>) {
13 if (process.env.NODE_ENV === 'production') {
14 newrelic.recordCustomEvent(eventType, attributes);
15 }
16}
17
18// Wrapper dla API calls z automatycznym śledzeniem
19export async function instrumentedApiCall<T>(
20 name: string,
21 apiCall: () => Promise<T>
22): Promise<T> {
23 const startTime = Date.now();
24
25 try {
26 const result = await apiCall();
27
28 // Zapisz metryki sukcesu
29 recordCustomMetric(`Custom/API/${name}/Duration`, Date.now() - startTime);
30 recordCustomEvent('APICall', {
31 name,
32 status: 'success',
33 duration: Date.now() - startTime,
34 });
35
36 return result;
37 } catch (error) {
38 // Zapisz metryki błędu
39 recordCustomMetric(`Custom/API/${name}/Error`, 1);
40 recordCustomEvent('APICall', {
41 name,
42 status: 'error',
43 duration: Date.now() - startTime,
44 error: error.message,
45 });
46
47 throw error;
48 }
49}W Metropolii Quantum 2150, każda interakcja użytkownika jest monitorowana przez zaawansowane systemy analityczne. Vercel Analytics to natywne rozwiązanie monitoringu dla aplikacji Next.js, zaprojektowane z myślą o prywatności użytkowników i zgodności z GDPR. W przeciwieństwie do Google Analytics, Vercel Analytics działa bezpośrednio w Vercel Edge Network, co zapewnia minimalne opóźnienia i dokładne pomiary.
Zanim zdecydujesz się na wybór narzędzia analitycznego, warto zrozumieć kluczowe różnice:
Vercel Analytics:
Google Analytics 4:
Real User Monitoring to technika zbierania danych o rzeczywistych doświadczeniach użytkowników, a nie syntetycznych testów. Vercel Analytics automatycznie śledzi wszystkie kluczowe metryki wydajności.
1npm install @vercel/analytics
2# lub
3yarn add @vercel/analytics
4# lub
5pnpm add @vercel/analytics1// app/layout.tsx
2import { Analytics } from '@vercel/analytics/react';
3
4export default function RootLayout({
5 children
6}: {
7 children: React.ReactNode
8}) {
9 return (
10 <html lang="pl">
11 <body>
12 {children}
13 <Analytics />
14 </body>
15 </html>
16 );
17}To wszystko! Vercel Analytics automatycznie rozpocznie śledzenie:
Core Web Vitals to kluczowe metryki wydajności web, które Google używa jako sygnały rankingowe. Vercel Analytics automatycznie śledzi wszystkie trzy metryki:
1. Largest Contentful Paint (LCP)
2. First Input Delay (FID) / Interaction to Next Paint (INP)
3. Cumulative Layout Shift (CLS)
Możesz programatically dostać się do metryk Web Vitals:
1// app/web-vitals.tsx
2'use client';
3
4import { useReportWebVitals } from 'next/web-vitals';
5
6export function WebVitals() {
7 useReportWebVitals((metric) => {
8 console.log(metric);
9
10 // Możesz wysłać metryki do własnego endpointu
11 if (metric.label === 'web-vital') {
12 fetch('/api/analytics/web-vitals', {
13 method: 'POST',
14 body: JSON.stringify(metric),
15 headers: {
16 'Content-Type': 'application/json'
17 }
18 });
19 }
20 });
21
22 return null;
23}1// app/layout.tsx
2import { Analytics } from '@vercel/analytics/react';
3import { WebVitals } from './web-vitals';
4
5export default function RootLayout({ children }: { children: React.ReactNode }) {
6 return (
7 <html lang="pl">
8 <body>
9 {children}
10 <Analytics />
11 <WebVitals />
12 </body>
13 </html>
14 );
15}Vercel Analytics pozwala śledzić custom events dla specyficznych akcji użytkowników:
1'use client';
2
3import { track } from '@vercel/analytics';
4
5export default function NewsletterForm() {
6 const handleSubscribe = async (email: string) => {
7 try {
8 await subscribeToNewsletter(email);
9
10 // Śledź udaną subskrypcję
11 track('Newsletter Subscribe', {
12 email_domain: email.split('@')[1],
13 source: 'homepage'
14 });
15
16 } catch (error) {
17 // Śledź błąd
18 track('Newsletter Subscribe Error', {
19 error: error.message
20 });
21 }
22 };
23
24 return (
25 <form onSubmit={(e) => {
26 e.preventDefault();
27 const email = e.target.email.value;
28 handleSubscribe(email);
29 }}>
30 <input type="email" name="email" required />
31 <button type="submit">Subskrybuj</button>
32 </form>
33 );
34}1'use client';
2
3import { track } from '@vercel/analytics';
4
5export default function ProductPage({ product }: { product: Product }) {
6 // Śledź wyświetlenie produktu
7 useEffect(() => {
8 track('Product View', {
9 product_id: product.id,
10 product_name: product.name,
11 product_category: product.category,
12 product_price: product.price
13 });
14 }, [product]);
15
16 // Śledź dodanie do koszyka
17 const handleAddToCart = () => {
18 track('Add to Cart', {
19 product_id: product.id,
20 quantity: 1,
21 value: product.price
22 });
23
24 addToCart(product);
25 };
26
27 // Śledź rozpoczęcie checkout
28 const handleCheckout = () => {
29 track('Begin Checkout', {
30 value: cartTotal,
31 items: cartItems.length
32 });
33
34 router.push('/checkout');
35 };
36
37 return (
38 <div>
39 <h1>{product.name}</h1>
40 <p>{product.price} PLN</p>
41 <button onClick={handleAddToCart}>Dodaj do koszyka</button>
42 <button onClick={handleCheckout}>Kup teraz</button>
43 </div>
44 );
45}Speed Insights to rozszerzenie Analytics, które dostarcza szczegółowe informacje o wydajności aplikacji dla prawdziwych użytkowników.
1npm install @vercel/speed-insights1// app/layout.tsx
2import { Analytics } from '@vercel/analytics/react';
3import { SpeedInsights } from '@vercel/speed-insights/next';
4
5export default function RootLayout({ children }: { children: React.ReactNode }) {
6 return (
7 <html lang="pl">
8 <body>
9 {children}
10 <Analytics />
11 <SpeedInsights />
12 </body>
13 </html>
14 );
15}Performance Score
Field Data (RUM)
Lab Data
Opportunities
Możesz łączyć Vercel Analytics z innymi narzędziami dla pełniejszego obrazu:
1// app/layout.tsx
2import { Analytics } from '@vercel/analytics/react';
3import { SpeedInsights } from '@vercel/speed-insights/next';
4import { GoogleAnalytics } from '@next/third-parties/google';
5
6export default function RootLayout({ children }: { children: React.ReactNode }) {
7 return (
8 <html lang="pl">
9 <body>
10 {children}
11
12 {/* Vercel Analytics - dla Core Web Vitals i podstawowych metryk */}
13 <Analytics />
14 <SpeedInsights />
15
16 {/* Google Analytics - dla zaawansowanej segmentacji */}
17 <GoogleAnalytics gaId={process.env.NEXT_PUBLIC_GA_ID} />
18 </body>
19 </html>
20 );
21}1// app/layout.tsx
2import { Analytics } from '@vercel/analytics/react';
3
4export default function RootLayout({ children }: { children: React.ReactNode }) {
5 return (
6 <html lang="pl">
7 <body>
8 {children}
9
10 {/* Tylko w produkcji */}
11 {process.env.NODE_ENV === 'production' && <Analytics />}
12 </body>
13 </html>
14 );
15}1// lib/analytics.ts
2import { track } from '@vercel/analytics';
3
4export const analytics = {
5 // User events
6 user: {
7 signup: (method: string) => track('User Signup', { method }),
8 login: (method: string) => track('User Login', { method }),
9 logout: () => track('User Logout')
10 },
11
12 // E-commerce events
13 ecommerce: {
14 viewProduct: (productId: string) =>
15 track('Product View', { product_id: productId }),
16 addToCart: (productId: string, value: number) =>
17 track('Add to Cart', { product_id: productId, value }),
18 purchase: (orderId: string, value: number) =>
19 track('Purchase', { order_id: orderId, value })
20 },
21
22 // Engagement events
23 engagement: {
24 shareContent: (contentType: string, method: string) =>
25 track('Share', { content_type: contentType, method }),
26 search: (query: string) =>
27 track('Search', { search_term: query }),
28 playVideo: (videoId: string) =>
29 track('Video Play', { video_id: videoId })
30 }
31};1'use client';
2
3import { track } from '@vercel/analytics';
4
5export default function CheckoutFlow() {
6 // Step 1: View cart
7 useEffect(() => {
8 track('Checkout Step 1 - Cart', {
9 items: cart.length,
10 value: cartTotal
11 });
12 }, []);
13
14 // Step 2: Shipping info
15 const handleShippingSubmit = () => {
16 track('Checkout Step 2 - Shipping', {
17 value: cartTotal
18 });
19 };
20
21 // Step 3: Payment
22 const handlePaymentSubmit = () => {
23 track('Checkout Step 3 - Payment', {
24 value: cartTotal,
25 payment_method: selectedPaymentMethod
26 });
27 };
28
29 // Step 4: Confirmation
30 const handleOrderComplete = (orderId: string) => {
31 track('Purchase Complete', {
32 order_id: orderId,
33 value: cartTotal,
34 items: cart.length
35 });
36 };
37
38 return <CheckoutSteps />;
39}Vercel Analytics Dashboard oferuje:
Overview
Audiences
Real-time
Web Vitals
Custom Events
Free tier:
Pro tier ($10/member/month):
Enterprise:
Możesz dynamicznie kontrolować feature flags bazując na analytics:
1// lib/feature-flags.ts
2import { get } from '@vercel/edge-config';
3import { track } from '@vercel/analytics';
4
5export async function checkFeatureFlag(flagName: string): Promise<boolean> {
6 const isEnabled = await get(flagName);
7
8 // Śledź użycie feature flag
9 if (isEnabled) {
10 track('Feature Flag Enabled', { flag: flagName });
11 }
12
13 return isEnabled || false;
14}W Metropolii Quantum 2150, gdzie każda milisekunda ma znaczenie, Vercel Analytics dostarcza kwantowej precyzji w monitoringu aplikacji Next.js:
Kluczowe zalety:
Kiedy używać Vercel Analytics:
Kiedy rozważyć alternatywy:
Vercel Analytics to idealne rozwiązanie dla większości aplikacji Next.js, oferujące doskonałą równowagę między prostotą, wydajnością i funkcjonalnością. W połączeniu z Speed Insights tworzy kompletny system monitoringu wydajności aplikacji w czasie rzeczywistym!
Dla bardziej zaawansowanego monitoringu możesz skonfigurować własny stack z Prometheus i Grafana.
1// lib/metrics.ts
2import client from 'prom-client';
3
4// Rejestr metryk
5const register = new client.Registry();
6
7// Metryki HTTP
8const httpRequestDuration = new client.Histogram({
9 name: 'http_request_duration_seconds',
10 help: 'Duration of HTTP requests in seconds',
11 labelNames: ['method', 'route', 'status_code'],
12 buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10],
13});
14
15const httpRequestTotal = new client.Counter({
16 name: 'http_requests_total',
17 help: 'Total number of HTTP requests',
18 labelNames: ['method', 'route', 'status_code'],
19});
20
21// Metryki biznesowe
22const userRegistrations = new client.Counter({
23 name: 'user_registrations_total',
24 help: 'Total number of user registrations',
25});
26
27const ordersTotal = new client.Counter({
28 name: 'orders_total',
29 help: 'Total number of orders',
30 labelNames: ['status'],
31});
32
33// Zarejestruj metryki
34register.registerMetric(httpRequestDuration);
35register.registerMetric(httpRequestTotal);
36register.registerMetric(userRegistrations);
37register.registerMetric(ordersTotal);
38
39// Dodaj domyślne metryki Node.js
40client.collectDefaultMetrics({ register });
41
42export {
43 register,
44 httpRequestDuration,
45 httpRequestTotal,
46 userRegistrations,
47 orderTotal
48};1// middleware.ts
2import { NextRequest, NextResponse } from 'next/server';
3import { httpRequestDuration, httpRequestTotal } from '@/lib/metrics';
4
5export function middleware(request: NextRequest) {
6 const start = Date.now();
7
8 // Kontynuuj przetwarzanie żądania
9 const response = NextResponse.next();
10
11 // Zapisz metryki po przetworzeniu żądania
12 response.headers.set('x-response-time', (Date.now() - start).toString());
13
14 // Async zapisanie metryk (nie blokuje odpowiedzi)
15 setImmediate(() => {
16 const duration = (Date.now() - start) / 1000;
17 const labels = {
18 method: request.method,
19 route: request.nextUrl.pathname,
20 status_code: response.status.toString(),
21 };
22
23 httpRequestDuration.observe(labels, duration);
24 httpRequestTotal.inc(labels);
25 });
26
27 return response;
28}
29
30export const config = {
31 matcher: [
32 '/((?!api|_next/static|_next/image|favicon.ico).*)',
33 ],
34};1// app/api/metrics/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3import { register } from '@/lib/metrics';
4
5export async function GET(request: NextRequest) {
6 const metrics = await register.metrics();
7
8 return new NextResponse(metrics, {
9 headers: {
10 'Content-Type': register.contentType,
11 },
12 });
13}Ważnym aspektem monitoringu jest automatyczne powiadamianie o problemach.
1// lib/alerts.ts
2import { IncomingWebhook } from '@slack/webhook';
3
4const webhook = new IncomingWebhook(process.env.SLACK_WEBHOOK_URL);
5
6export async function sendSlackAlert(message: string, severity: 'info' | 'warning' | 'error') {
7 const colors = {
8 info: '#36a64f',
9 warning: '#ffcc00',
10 error: '#ff0000',
11 };
12
13 try {
14 await webhook.send({
15 attachments: [
16 {
17 color: colors[severity],
18 fields: [
19 {
20 title: 'Alert',
21 value: message,
22 short: false,
23 },
24 {
25 title: 'Time',
26 value: new Date().toISOString(),
27 short: true,
28 },
29 {
30 title: 'Environment',
31 value: process.env.NODE_ENV,
32 short: true,
33 },
34 ],
35 },
36 ],
37 });
38 } catch (error) {
39 console.error('Failed to send Slack alert:', error);
40 }
41}
42
43// Funkcja do sprawdzania thresholdów i wysyłania alertów
44export function checkPerformanceThresholds(metrics: {
45 lcp: number;
46 fid: number;
47 cls: number;
48}) {
49 if (metrics.lcp > 2500) {
50 sendSlackAlert(`High LCP detected: ${metrics.lcp}ms`, 'warning');
51 }
52
53 if (metrics.fid > 100) {
54 sendSlackAlert(`High FID detected: ${metrics.fid}ms`, 'warning');
55 }
56
57 if (metrics.cls > 0.1) {
58 sendSlackAlert(`High CLS detected: ${metrics.cls}`, 'warning');
59 }
60}1// lib/uptime-monitor.ts
2import { sendSlackAlert } from './alerts';
3
4interface HealthCheckResult {
5 service: string;
6 status: 'healthy' | 'unhealthy';
7 responseTime: number;
8 timestamp: Date;
9}
10
11export async function checkServiceHealth(url: string, service: string): Promise<HealthCheckResult> {
12 const start = Date.now();
13
14 try {
15 const response = await fetch(url, {
16 method: 'GET',
17 timeout: 10000, // 10 second timeout
18 });
19
20 const responseTime = Date.now() - start;
21
22 if (response.ok) {
23 return {
24 service,
25 status: 'healthy',
26 responseTime,
27 timestamp: new Date(),
28 };
29 } else {
30 await sendSlackAlert(`Service ${service} returned ${response.status}`, 'error');
31 return {
32 service,
33 status: 'unhealthy',
34 responseTime,
35 timestamp: new Date(),
36 };
37 }
38 } catch (error) {
39 await sendSlackAlert(`Service ${service} is unreachable: ${error.message}`, 'error');
40 return {
41 service,
42 status: 'unhealthy',
43 responseTime: Date.now() - start,
44 timestamp: new Date(),
45 };
46 }
47}
48
49// Funkcja do uruchamiania health checków
50export async function runHealthChecks() {
51 const services = [
52 { name: 'Main App', url: 'https://your-app.com/api/health' },
53 { name: 'Database', url: 'https://your-app.com/api/health/db' },
54 { name: 'External API', url: 'https://api.external-service.com/health' },
55 ];
56
57 const results = await Promise.allSettled(
58 services.map(service => checkServiceHealth(service.url, service.name))
59 );
60
61 results.forEach((result, index) => {
62 if (result.status === 'fulfilled') {
63 console.log(`Health check for ${services[index].name}:`, result.value);
64 } else {
65 console.error(`Health check failed for ${services[index].name}:`, result.reason);
66 }
67 });
68}1// app/admin/monitoring/page.tsx
2import { getWebVitalMetrics, getErrorMetrics } from '@/lib/analytics';
3import { LineChart, BarChart } from '@/components/Charts';
4
5export default async function MonitoringDashboard() {
6 const webVitals = await getWebVitalMetrics();
7 const errors = await getErrorMetrics();
8
9 return (
10 <div className="p-6">
11 <h1 className="text-3xl font-bold mb-8">Monitoring Dashboard</h1>
12
13 <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
14 {/* Core Web Vitals */}
15 <div className="bg-white p-6 rounded-lg shadow">
16 <h2 className="text-xl font-semibold mb-4">Core Web Vitals</h2>
17 <LineChart
18 data={webVitals}
19 categories={['lcp', 'fid', 'cls']}
20 xKey="timestamp"
21 />
22 </div>
23
24 {/* Error Rate */}
25 <div className="bg-white p-6 rounded-lg shadow">
26 <h2 className="text-xl font-semibold mb-4">Error Rate</h2>
27 <BarChart
28 data={errors}
29 category="count"
30 xKey="hour"
31 />
32 </div>
33 </div>
34
35 {/* Real-time metrics */}
36 <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
37 <MetricCard title="Active Users" value="1,234" change="+5.2%" />
38 <MetricCard title="Response Time" value="156ms" change="-2.1%" />
39 <MetricCard title="Error Rate" value="0.01%" change="-50%" />
40 <MetricCard title="Uptime" value="99.9%" change="+0.1%" />
41 </div>
42 </div>
43 );
44}
45
46function MetricCard({ title, value, change }: {
47 title: string;
48 value: string;
49 change: string;
50}) {
51 const isPositive = change.startsWith('+');
52 const isNegative = change.startsWith('-');
53
54 return (
55 <div className="bg-white p-4 rounded-lg shadow">
56 <h3 className="text-sm font-medium text-gray-500">{title}</h3>
57 <div className="mt-2 flex items-baseline">
58 <p className="text-2xl font-semibold text-gray-900">{value}</p>
59 <p className={`ml-2 text-sm font-medium ${
60 isPositive ? 'text-green-600' : isNegative ? 'text-red-600' : 'text-gray-500'
61 }`}>
62 {change}
63 </p>
64 </div>
65 </div>
66 );
67}1// lib/sampling.ts
2
3// Funkcja do określania, czy zdarzenie powinno być śledzone
4export function shouldSample(sampleRate: number): boolean {
5 return Math.random() < sampleRate;
6}
7
8// Adaptive sampling - więcej próbek dla błędów
9export function adaptiveSample(isError: boolean): boolean {
10 return isError ? shouldSample(1.0) : shouldSample(0.1);
11}
12
13// Sampling based on user tier
14export function userTierSample(userTier: 'free' | 'premium' | 'enterprise'): boolean {
15 const rates = {
16 free: 0.05, // 5% dla darmowych użytkowników
17 premium: 0.25, // 25% dla premium
18 enterprise: 1.0, // 100% dla enterprise
19 };
20
21 return shouldSample(rates[userTier]);
22}1// lib/performance-monitoring.ts
2
3// Buffer dla metryk - zbieraj i wysyłaj partiami
4class MetricsBuffer {
5 private buffer: any[] = [];
6 private maxSize = 100;
7 private flushInterval = 30000; // 30 sekund
8
9 constructor() {
10 // Automatyczne oproznianie bufora
11 setInterval(() => this.flush(), this.flushInterval);
12 }
13
14 add(metric: any) {
15 this.buffer.push(metric);
16
17 if (this.buffer.length >= this.maxSize) {
18 this.flush();
19 }
20 }
21
22 private async flush() {
23 if (this.buffer.length === 0) return;
24
25 const metricsToSend = [...this.buffer];
26 this.buffer = [];
27
28 try {
29 await fetch('/api/metrics/batch', {
30 method: 'POST',
31 headers: { 'Content-Type': 'application/json' },
32 body: JSON.stringify({ metrics: metricsToSend }),
33 });
34 } catch (error) {
35 console.error('Failed to send metrics batch:', error);
36 // Wróć metryki do bufora w przypadku błędu
37 this.buffer.unshift(...metricsToSend);
38 }
39 }
40}
41
42export const metricsBuffer = new MetricsBuffer();Monitoring wydajności i analityka błędów to kluczowe elementy każdej produkcyjnej aplikacji Next.js. Kompleksowe podejście do monitoringu obejmuje:
Wybierając narzędzia monitoringu, rozważ:
Pamiętaj o balansie między głębokością monitoringu a wpływem na wydajność aplikacji. Używaj sampling, bufferingu i optymalizuj zapytania, aby monitoring nie wpływał negatywnie na doświadczenie użytkownika.