feat(analytics): integrate Rybbit Analytics with error tracking and session replay
- Added rewrites in next.config.mjs for Rybbit Analytics to bypass adblockers. - Implemented ErrorBoundary and ErrorTracking components in layout.tsx for enhanced error handling. - Included a script for analytics tracking in the root layout. - Updated NutritionGrid in product page to handle potential undefined nutrition data.
This commit is contained in:
1 parent
c99c82c837
commit
0eeb63bbde
6 files changed
+188
-7
No files matched your search
@@ -0,0 +1,78 @@
|
||||
"use client";
|
||||
|
||||
import React, { Component, ErrorInfo, ReactNode } from "react";
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
fallback?: ReactNode;
|
||||
}
|
||||
|
||||
interface State {
|
||||
hasError: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error Boundary para capturar erros React e enviar ao Rybbit
|
||||
*/
|
||||
export class ErrorBoundary extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { hasError: false, error: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): State {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
// Enviar erro ao Rybbit se disponível
|
||||
if (typeof window !== "undefined" && (window as any).rybbit) {
|
||||
try {
|
||||
(window as any).rybbit.track("error", {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
componentStack: errorInfo.componentStack,
|
||||
errorName: error.name,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
} catch (trackingError) {
|
||||
console.error("[ErrorBoundary] Falha ao enviar erro ao Rybbit:", trackingError);
|
||||
}
|
||||
}
|
||||
|
||||
console.error("[ErrorBoundary] Erro capturado:", error, errorInfo);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
if (this.props.fallback) {
|
||||
return this.props.fallback;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white flex items-center justify-center p-6 dark:bg-[#030712]">
|
||||
<div className="max-w-md w-full text-center space-y-6 border border-slate-200 p-10 rounded-[2.5rem] bg-slate-50 dark:bg-red-950/10 dark:border-red-900/20">
|
||||
<h1 className="text-2xl font-black text-slate-900 dark:text-white uppercase tracking-tighter">
|
||||
Erro Inesperado
|
||||
</h1>
|
||||
<p className="text-slate-600 text-sm dark:text-slate-400 font-medium">
|
||||
{this.state.error?.message || "Algo deu errado. Por favor, recarregue a página."}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => {
|
||||
this.setState({ hasError: false, error: null });
|
||||
window.location.reload();
|
||||
}}
|
||||
className="w-full bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition-colors"
|
||||
>
|
||||
Recarregar Página
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
|
||||
/**
|
||||
* Componente para configurar error tracking global do Rybbit
|
||||
* Captura erros JavaScript não tratados e Promise rejections
|
||||
*/
|
||||
export function ErrorTracking() {
|
||||
useEffect(() => {
|
||||
// Handler para erros JavaScript não capturados
|
||||
const handleError = (event: ErrorEvent) => {
|
||||
if (typeof window !== "undefined" && (window as any).rybbit) {
|
||||
try {
|
||||
(window as any).rybbit.track("error", {
|
||||
message: event.message,
|
||||
filename: event.filename,
|
||||
lineno: event.lineno,
|
||||
colno: event.colno,
|
||||
stack: event.error?.stack,
|
||||
errorName: event.error?.name,
|
||||
timestamp: new Date().toISOString(),
|
||||
type: "javascript_error",
|
||||
});
|
||||
} catch (trackingError) {
|
||||
console.error("[ErrorTracking] Falha ao enviar erro ao Rybbit:", trackingError);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Handler para Promise rejections não tratadas
|
||||
const handleUnhandledRejection = (event: PromiseRejectionEvent) => {
|
||||
if (typeof window !== "undefined" && (window as any).rybbit) {
|
||||
try {
|
||||
const error = event.reason instanceof Error ? event.reason : new Error(String(event.reason));
|
||||
|
||||
(window as any).rybbit.track("error", {
|
||||
message: error.message || String(event.reason),
|
||||
stack: error.stack,
|
||||
errorName: error.name || "UnhandledPromiseRejection",
|
||||
timestamp: new Date().toISOString(),
|
||||
type: "unhandled_promise_rejection",
|
||||
});
|
||||
} catch (trackingError) {
|
||||
console.error("[ErrorTracking] Falha ao enviar rejection ao Rybbit:", trackingError);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Adicionar listeners
|
||||
window.addEventListener("error", handleError);
|
||||
window.addEventListener("unhandledrejection", handleUnhandledRejection);
|
||||
|
||||
// Cleanup
|
||||
return () => {
|
||||
window.removeEventListener("error", handleError);
|
||||
window.removeEventListener("unhandledrejection", handleUnhandledRejection);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
+16
-1
@@ -1,7 +1,10 @@
|
||||
import "./globals.css";
|
||||
import { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import Script from "next/script";
|
||||
import { ThemeProvider } from "@/app/providers";
|
||||
import { ErrorBoundary } from "@/app/components/ErrorBoundary";
|
||||
import { ErrorTracking } from "@/app/components/ErrorTracking";
|
||||
|
||||
// Use Inter como fallback já que é uma fonte similar à Geist
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
@@ -79,7 +82,19 @@ export default function RootLayout({
|
||||
return (
|
||||
<html lang="pt-BR" suppressHydrationWarning className={inter.className}>
|
||||
<body className="min-h-screen bg-background antialiased">
|
||||
<ThemeProvider>{children}</ThemeProvider>
|
||||
<ErrorBoundary>
|
||||
<ThemeProvider>
|
||||
{children}
|
||||
<ErrorTracking />
|
||||
</ThemeProvider>
|
||||
</ErrorBoundary>
|
||||
<Script
|
||||
src="/analytics/script.js"
|
||||
data-site-id="undefined"
|
||||
data-replay="true"
|
||||
data-error-tracking="true"
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -173,7 +173,7 @@ export default function ProdutoPage() {
|
||||
<div className="w-2 h-6 bg-blue-600 rounded-full" />
|
||||
Composição Nutricional <span className="text-slate-400 font-mono text-xs font-normal">/ 100g</span>
|
||||
</h3>
|
||||
<NutritionGrid nutritionData={formatNutritionData(product)} />
|
||||
<NutritionGrid nutritionData={formatNutritionData(product) || {}} />
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
|
||||
@@ -25,6 +25,32 @@ const nextConfig = {
|
||||
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
|
||||
},
|
||||
|
||||
// Proxy reverso para bypass de adblockers (Rybbit Analytics)
|
||||
// Inclui suporte para session replay e error tracking
|
||||
async rewrites() {
|
||||
const rybbitHost = process.env.NEXT_PUBLIC_RYBBIT_HOST || "https://analytics.evandro.dev.br/api";
|
||||
|
||||
return [
|
||||
{
|
||||
source: "/analytics/:match*",
|
||||
destination: `${rybbitHost}/:match*`,
|
||||
},
|
||||
// Endpoints específicos para garantir funcionamento de replay e error tracking
|
||||
{
|
||||
source: "/analytics/v1/replay",
|
||||
destination: `${rybbitHost}/v1/replay`,
|
||||
},
|
||||
{
|
||||
source: "/analytics/v1/vitals",
|
||||
destination: `${rybbitHost}/v1/vitals`,
|
||||
},
|
||||
{
|
||||
source: "/analytics/v1/errors",
|
||||
destination: `${rybbitHost}/v1/errors`,
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
// Headers de segurança e performance
|
||||
async headers() {
|
||||
return [
|
||||
|
||||
+5
-5
@@ -5,12 +5,12 @@
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"lint": "next lint",
|
||||
"postinstall": "./node_modules/.bin/prisma generate",
|
||||
"build": "./node_modules/.bin/prisma generate && ./node_modules/.bin/prisma migrate deploy && next build",
|
||||
"postinstall": "prisma generate",
|
||||
"build": "prisma generate && prisma migrate deploy && next build",
|
||||
"start": "next start",
|
||||
"db:push": "./node_modules/.bin/prisma db push",
|
||||
"db:generate": "./node_modules/.bin/prisma generate",
|
||||
"db:migrate": "./node_modules/.bin/prisma migrate deploy"
|
||||
"db:push": "prisma db push",
|
||||
"db:generate": "prisma generate",
|
||||
"db:migrate": "prisma migrate deploy"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.22.0",
|
||||
|
||||
Reference in new issue
Block a user