Cours Vibe coding avec l'IA · Module 12: Clone de Medium
Surveillance des performances - Sentry/Analytics
Dans cette leçon2
Surveillance des erreurs et des performances en production.
Configuration de Sentry
1npm install @sentry/nextjs1// sentry.client.config.ts
2import * as Sentry from "@sentry/nextjs";
3
4Sentry.init({
5 dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
6 tracesSampleRate: 1.0,
7 environment: process.env.NODE_ENV
8});Limites d'erreur (Error Boundaries)
1'use client';
2
3import { Component, ReactNode } from 'react';
4import * as Sentry from '@sentry/nextjs';
5
6class ErrorBoundary extends Component<
7 { children: ReactNode },
8 { hasError: boolean }
9> {
10 state = { hasError: false };
11
12 static getDerivedStateFromError() {
13 return { hasError: true };
14 }
15
16 componentDidCatch(error: Error, errorInfo: any) {
17 Sentry.captureException(error, { contexts: { react: errorInfo } });
18 }
19
20 render() {
21 if (this.state.hasError) {
22 return <div>Une erreur est survenue</div>;
23 }
24 return this.props.children;
25 }
26}À bientôt !
Code de cette leçon : App.tsx
1'use client';
2
3import { useState } from 'react';
4
5interface Response {
6 id: string;
7 author: {
8 name: string;
9 avatar: string;
10 };
11 content: string;
12 claps: number;
13 createdAt: Date;
14 replies: Response[];
15}
16
17const mockResponses: Response[] = [
18 {
19 id: '1',
20 author: {
21 name: 'Capitaine Barbe-Rousse',
22 avatar: 'https://i.pravatar.cc/150?img=12'
23 },
24 content: 'Super article ! J\'ai vraiment aimé lire sur ce sujet. Les exemples étaient particulièrement utiles.',
25 claps: 24,
26 createdAt: new Date(Date.now() - 1000 * 60 * 60 * 2),
27 replies: [
28 {
29 id: '2',
30 author: {
31 name: 'Matelot Sam',
32 avatar: 'https://i.pravatar.cc/150?img=33'
33 },
34 content: 'Je suis d\'accord ! Les exemples pratiques ont rendu ça facile à comprendre.',
35 claps: 8,
36 createdAt: new Date(Date.now() - 1000 * 60 * 30),
37 replies: []
38 }
39 ]
40 },
41 {
42 id: '3',
43 author: {
44 name: 'Navigatrice Nancy',
45 avatar: 'https://i.pravatar.cc/150?img=45'
46 },
47 content: 'J\'adorerais voir plus de contenu comme celui-ci. Merci du partage !',
48 claps: 15,
49 createdAt: new Date(Date.now() - 1000 * 60 * 60),
50 replies: []
51 }
52];
53
54function ResponseItem({ response }: { response: Response }) {
55 return (
56 <div className="border-b border-gray-200 pb-6">
57 <div className="flex items-start gap-3">
58 <img
59 src={response.author.avatar}
60 alt={response.author.name}
61 className="w-10 h-10 rounded-full"
62 />
63 <div className="flex-1">
64 <div className="flex items-center gap-2 mb-2">
65 <span className="font-semibold">{response.author.name}</span>
66 <span className="text-sm text-gray-500">il y a 2 h</span>
67 </div>
68 <p className="text-gray-800 leading-relaxed mb-3">
69 {response.content}
70 </p>
71 <div className="flex items-center gap-4 text-sm text-gray-500">
72 <button className="flex items-center gap-1 hover:text-green-600">
73 <span>Bravo</span>
74 <span>{response.claps}</span>
75 </button>
76 <button className="hover:text-gray-700">Répondre</button>
77 </div>
78
79 {response.replies.length > 0 && (
80 <div className="mt-4 pl-6 border-l-2 border-gray-100 space-y-4">
81 {response.replies.map(reply => (
82 <ResponseItem key={reply.id} response={reply} />
83 ))}
84 </div>
85 )}
86 </div>
87 </div>
88 </div>
89 );
90}
91
92export default function ResponseSection() {
93 const [content, setContent] = useState('');
94 const [responses, setResponses] = useState(mockResponses);
95
96 const handleSubmit = () => {
97 if (!content.trim()) return;
98
99 alert(`Réponse publiée : ${content}`);
100 setContent('');
101 };
102
103 return (
104 <div className="min-h-screen bg-white">
105 <div className="max-w-2xl mx-auto px-8 py-12">
106 <h2 className="text-2xl font-bold mb-6">
107 Réponses ({responses.length})
108 </h2>
109
110 {/* Write Response */}
111 <div className="mb-8 border border-gray-200 rounded-lg p-4">
112 <textarea
113 value={content}
114 onChange={(e) => setContent(e.target.value)}
115 placeholder="Qu'en penses-tu ?"
116 className="w-full outline-none resize-none text-lg"
117 rows={3}
118 />
119 <div className="flex justify-end mt-2">
120 <button
121 onClick={handleSubmit}
122 disabled={!content.trim()}
123 className="px-6 py-2 bg-green-600 text-white rounded-full font-semibold hover:bg-green-700 disabled:bg-gray-300 disabled:cursor-not-allowed"
124 >
125 Répondre
126 </button>
127 </div>
128 </div>
129
130 {/* Responses */}
131 <div className="space-y-6">
132 {responses.map(response => (
133 <ResponseItem key={response.id} response={response} />
134 ))}
135 </div>
136 </div>
137 </div>
138 );
139}Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. À quoi sert Sentry dans une application Next.js ?
2. Quelle méthode de l'Error Boundary sert à signaler l'erreur à Sentry ?
Tâches pratiques dans le jeu
- Éditeur de code
Error Boundary avec envoi de rapports à Sentry
- Tri vertical
Ordonne les étapes de gestion d'une erreur avec un Error Boundary :