Пошук уроків, статей та іншого контенту
Підключите Error Tracking, додасте контекст до винятків і навчитеся швидко знаходити причини збоїв.
Логи повідомляють, що застосунок щось записав. Error Tracking спеціально збирає винятки та додає до них інформацію, потрібну для діагностики:
стек викликів;
URL і HTTP-метод;
середовище виконання;
версію застосунку;
користувача;
теги та додатковий контекст;
breadcrumbs — події, які відбулися перед помилкою.
У Next.js для цього часто використовують Sentry. Він підтримує серверний код, Client Components, Route Handlers, Server Actions і Edge Runtime.
У цьому уроці використовується App Router.
Створіть або відкрийте Next.js-проєкт і запустіть майстер налаштування:
npx @sentry/wizard@latest -i nextjsМайстер зазвичай:
встановлює @sentry/nextjs;
створює файли ініціалізації для клієнта, сервера та Edge Runtime;
оновлює next.config;
налаштовує завантаження source maps під час збірки.
Якщо Sentry вже налаштований у проєкті, перевірте, що пакет встановлено:
npm install @sentry/nextjsDSN — це адреса, за якою SDK надсилає події до проєкту Sentry. Додайте її до змінних середовища:
NEXT_PUBLIC_SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0
SENTRY_ENVIRONMENT=developmentРеальний DSN потрібно взяти з налаштувань проєкту Sentry.
Публічний DSN не є секретом: він може потрапляти до клієнтського JavaScript. Натомість токени для завантаження source maps мають зберігатися лише на сервері або в CI/CD.
У сучасному Next.js клієнтську ініціалізацію можна розмістити у файлі instrumentation-client.ts у корені проєкту:
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.SENTRY_ENVIRONMENT ?? "development",
// Для production значення підбирають відповідно до обсягу трафіку.
tracesSampleRate: 0.1,
});Для серверного коду створіть sentry.server.config.ts:
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.SENTRY_ENVIRONMENT ?? "development",
});А для Edge Runtime — sentry.edge.config.ts:
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.SENTRY_ENVIRONMENT ?? "development",
});Конкретний набір файлів може залежати від версії @sentry/nextjs і результату роботи майстра. Важливо, щоб SDK був ініціалізований у кожному середовищі, де потрібно відстежувати помилки.
Для ручного надсилання винятку використовуйте captureException:
import * as Sentry from "@sentry/nextjs";
export async function loadProfile(userId: string) {
try {
const response = await fetch(
`https://api.example.com/users/${userId}`,
{ cache: "no-store" },
);
if (!response.ok) {
throw new Error(`Не вдалося завантажити профіль: ${response.status}`);
}
return response.json();
} catch (error) {
Sentry.captureException(error);
throw error;
}
}Повторне throw важливе: Error Tracking не має приховувати помилку від основної логіки програми. Виклик captureException лише надсилає інформацію до Sentry, а не виправляє проблему.
Не потрібно вручну перехоплювати кожну помилку. Інтеграція Sentry автоматично збирає багато необроблених винятків. Ручне перехоплення потрібне, коли:
ви обробляєте помилку в try/catch;
хочете додати бізнес-контекст;
помилка не доходить до глобального обробника;
потрібно зафіксувати помилку перед поверненням контрольованої відповіді.
Самого повідомлення Error: Request failed часто недостатньо. Потрібно зрозуміти:
у якій функціональній області виникла помилка;
з яким замовленням або користувачем вона пов’язана;
який зовнішній сервіс викликав проблему;
які параметри були важливими для діагностики.
Для цього використовуйте withScope:
import * as Sentry from "@sentry/nextjs";
type PaymentInput = {
orderId: string;
amount: number;
currency: string;
};
export async function chargePayment(
input: PaymentInput,
userId: string,
) {
try {
const response = await fetch("https://payments.example.com/charge", {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify(input),
});
if (!response.ok) {
throw new Error(`Платіжний сервіс повернув ${response.status}`);
}
return await response.json();
} catch (error) {
Sentry.withScope((scope) => {
scope.setTag("feature", "payments");
scope.setTag("payment_provider", "example-payments");
scope.setUser({ id: userId });
scope.setContext("payment", {
orderId: input.orderId,
amount: input.amount,
currency: input.currency,
});
Sentry.captureException(error);
});
throw error;
}
}setTag додає індексовані поля, за якими зручно фільтрувати події:
scope.setTag("feature", "payments");
scope.setTag("operation", "charge");setUser пов’язує помилку з користувачем:
scope.setUser({ id: userId });За потреби можна передати й інші поля:
scope.setUser({
id: userId,
username: username,
});setContext додає структуровані дані, які допомагають розібратися в конкретній події:
scope.setContext("cart", {
itemCount: 3,
currency: "UAH",
couponApplied: true,
});setExtra підходить для додаткового значення, яке не потрібно використовувати як фільтр:
scope.setExtra("retryCount", retryCount);Дані, додані всередині withScope, стосуються лише події, яку надсилають у цьому блоці:
Sentry.withScope((scope) => {
scope.setTag("feature", "orders");
Sentry.captureException(error);
});Це безпечніше, ніж безконтрольно змінювати глобальний контекст. Контекст однієї помилки не повинен випадково потрапити до наступних подій.
Розглянемо Route Handler, який створює замовлення:
import { NextResponse } from "next/server";
import * as Sentry from "@sentry/nextjs";
type CreateOrderBody = {
productId: string;
quantity: number;
};
export async function POST(request: Request) {
const userId = request.headers.get("x-user-id") ?? "anonymous";
try {
const body = (await request.json()) as CreateOrderBody;
if (!body.productId || body.quantity < 1) {
return NextResponse.json(
{ error: "Некоректні дані замовлення" },
{ status: 400 },
);
}
// Тут зазвичай виконується запис до бази даних.
const order = {
id: crypto.randomUUID(),
productId: body.productId,
quantity: body.quantity,
};
return NextResponse.json(order, { status: 201 });
} catch (error) {
Sentry.withScope((scope) => {
scope.setTag("feature", "orders");
scope.setTag("route", "/api/orders");
scope.setUser({ id: userId });
scope.setContext("request", {
method: "POST",
path: "/api/orders",
});
Sentry.captureException(error);
});
return NextResponse.json(
{ error: "Внутрішня помилка сервера" },
{ status: 500 },
);
}
}Клієнту повертається загальне повідомлення, а деталі залишаються в Sentry. Не варто віддавати користувачу stack trace, назви таблиць, SQL-запити або внутрішні повідомлення бібліотек.
error.tsxУ сегменті App Router можна створити app/error.tsx. Це Client Component, який показує резервний інтерфейс, якщо під час рендерингу сегмента сталася помилка:
"use client";
import { useEffect } from "react";
import * as Sentry from "@sentry/nextjs";
type ErrorPageProps = {
error: Error & { digest?: string };
reset: () => void;
};
export default function ErrorPage({
error,
reset,
}: ErrorPageProps) {
useEffect(() => {
Sentry.withScope((scope) => {
scope.setTag("boundary", "app-error");
scope.setExtra("digest", error.digest);
Sentry.captureException(error);
});
}, [error]);
return (
<main>
<h1>Щось пішло не так</h1>
<p>Спробуйте повторити дію.</p>
<button type="button" onClick={() => reset()}>
Спробувати ще раз
</button>
</main>
);
}reset повторно намагається відрендерити сегмент. Це корисно для тимчасових помилок, але не вирішує саму причину збою.
У деяких конфігураціях інтеграція Sentry автоматично перехоплює помилки з Error Boundary. Якщо та сама подія вже збирається автоматично, ручний captureException може створити дублікати. Після налаштування перевірте події в Sentry і залиште лише один спосіб надсилання для конкретної помилки.
Контекст має допомагати розслідуванню, але не повинен розкривати секрети.
Не передавайте до Sentry:
паролі;
токени доступу;
ключі API;
повні номери банківських карток;
cookie;
повне тіло запиту без фільтрації;
персональні дані, які не потрібні для діагностики.
Наприклад, замість повного email можна передати внутрішній ідентифікатор:
Sentry.withScope((scope) => {
scope.setUser({ id: userId });
scope.setContext("checkout", {
orderId,
itemCount,
});
Sentry.captureException(error);
});Якщо потрібно додати заголовки або дані запиту, спочатку сформуйте безпечний об’єкт вручну. Не передавайте до Sentry весь об’єкт request.
Одна й та сама помилка в development і production має різне значення. Встановлюйте середовище явно:
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.SENTRY_ENVIRONMENT ?? "development",
});Для production задайте:
SENTRY_ENVIRONMENT=productionПід час production-збірки Sentry може завантажити source maps. Завдяки цьому stack trace з мінімізованого JavaScript перетворюється на зрозумілий стек із вихідними файлами та номерами рядків.
Токен для завантаження source maps потрібно додавати до секретів CI/CD, а не до .env у репозиторії. Після деплою перевірте, що подія містить:
environment: production;
правильну версію або release;
назву вихідного файлу;
коректний номер рядка;
читабельний stack trace.
Без source maps помилка може виглядати так:
at t (page-8f31a.js:1:18420)Із source maps вона зазвичай вказує на конкретний файл і рядок вашого коду.
Після появи події в Sentry дійте послідовно:
Перевірте назву issue та кількість повторів.
Відкрийте останню подію.
Перегляньте stack trace і знайдіть перший кадр вашого коду.
Перевірте environment і версію застосунку.
Проаналізуйте теги: feature, route, operation.
Перегляньте контекст користувача та бізнес-операції.
Перевірте breadcrumbs перед винятком.
Порівняйте час появи помилки з останнім деплоєм.
Відтворіть сценарій локально з такими самими безпечними вхідними даними.
Після виправлення перевірте, що нові події більше не створюються.
Корисний контекст має відповідати на питання:
Що робив користувач?
У якій частині системи сталася помилка?
З яким ресурсом вона пов’язана?
Який зовнішній сервіс або запит був залучений?
Яка версія застосунку працювала в цей момент?
Додайте тимчасовий тестовий компонент:
"use client";
import * as Sentry from "@sentry/nextjs";
export default function ErrorTrackingTest() {
function sendTestError() {
try {
throw new Error("Тестова помилка Error Tracking");
} catch (error) {
Sentry.withScope((scope) => {
scope.setTag("feature", "error-tracking-test");
scope.setContext("test", {
source: "manual-button",
});
Sentry.captureException(error);
});
}
}
return (
<button type="button" onClick={sendTestError}>
Створити тестову помилку
</button>
);
}Після натискання кнопки перевірте в Sentry:
чи з’явилася подія;
чи має вона тег feature=error-tracking-test;
чи присутній контекст test;
чи правильно визначене середовище;
чи читається stack trace.
Після перевірки видаліть цей компонент або обмежте його використання development-середовищем.
Виняток із повідомленням Request failed складно пов’язати з конкретною операцією.
Додавайте кілька стабільних тегів і структурований контекст:
scope.setTag("feature", "orders");
scope.setTag("operation", "create");
scope.setContext("order", { orderId });Це трапляється, коли помилку автоматично збирає інтеграція, а код додатково викликає captureException.
Перевірте кількість однакових подій і не надсилайте ту саму помилку вручну в кількох шарах.
Не передавайте цілі об’єкти запиту, заголовки, cookies або конфігурацію без фільтрації. Формуйте мінімальний безпечний контекст вручну.
console.error не замінює Error Tracking. Запис у консоль може бути недоступним після завершення запиту або загубитися серед інших повідомлень.
Для важливих винятків використовуйте Sentry.captureException.
Перевірте завантаження source maps під час production-збірки та наявність правильного release. Без цього локалізація помилки буде повільнішою.
Не повертайте error.message або stack trace у production-відповіді. Користувачеві достатньо загального повідомлення, а технічні деталі мають залишатися в Error Tracking.
@sentry/nextjs збирає помилки з клієнтського та серверного коду Next.js.
captureException вручну надсилає оброблений виняток до Sentry.
withScope дає змогу додати контекст лише до конкретної події.
setTag зручний для фільтрації, а setContext — для структурованих діагностичних даних.
У production потрібні правильне середовище, release та source maps.
Не передавайте до Error Tracking секрети й зайві персональні дані.
Спочатку перевіряйте stack trace, середовище, реліз, теги та події перед помилкою.
Не допускайте дублювання, якщо помилка вже збирається автоматично.