Fix: favicon 404 error and NIP-95 upload 500 error

**Motivations:**
- Corriger l'erreur 404 pour favicon.ico demandé par les navigateurs
- Corriger l'erreur 500 de l'API NIP-95 upload empêchant les uploads de fichiers

**Root causes:**
- Fichier favicon.ico manquant dans public/ causant des erreurs 404 répétées
- Incompatibilité entre form-data (npm) et fetch() natif de Node.js dans l'API NIP-95

**Correctifs:**
- Ajout de favicon.svg et mise à jour des références dans les pages
- Remplacement de fetch() par https/http natifs de Node.js dans nip95-upload.ts
- Amélioration de la gestion des erreurs et nettoyage des fichiers temporaires

**Evolutions:**
- Documentation des problèmes et solutions dans fixKnowledge/

**Pages affectées:**
- components/HomeView.tsx
- pages/docs.tsx
- pages/presentation.tsx
- pages/api/nip95-upload.ts
- features/account-creation-buttons-separation.md
- fixKnowledge/favicon-404-error.md (nouveau)
- fixKnowledge/nip95-upload-500-error.md (nouveau)
- public/favicon.svg (nouveau)
This commit is contained in:
Nicolas Cantu 2026-01-05 01:34:55 +01:00
parent 83e9029f9a
commit 065ab30828
8 changed files with 201 additions and 24 deletions

View File

@ -33,7 +33,7 @@ function HomeHead() {
content="Plateforme de publication d'articles scientifiques et de science-fiction avec sponsoring et rémunération des avis"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
</Head>
)
}

View File

@ -51,3 +51,5 @@ Aucun déploiement spécial nécessaire. Les modifications sont purement fronten
- Le composant `NoAccountActionButtons` respecte la limite de lignes de fonction
- La fonction `NoAccountView` respecte la limite de 40 lignes après refactorisation

View File

@ -0,0 +1,57 @@
# Problème : favicon.ico retourne 404
## Date
2025-01-27
## Problème
Le navigateur demandait automatiquement `/favicon.ico` mais le fichier n'existait pas dans le dossier `public/`, causant une erreur 404.
## Symptômes
- Console navigateur : `Failed to load resource: the server responded with a status of 404 ()`
- Erreur répétée sur toutes les pages : `favicon.ico:1 Failed to load resource: the server responded with a status of 404 ()`
- Aucun favicon affiché dans les onglets du navigateur
## Root cause
Le fichier `favicon.ico` n'existait pas dans le dossier `public/`, mais plusieurs pages référençaient `/favicon.ico` dans leur balise `<Head>` :
- `components/HomeView.tsx`
- `pages/presentation.tsx`
- `pages/docs.tsx`
Les navigateurs demandent automatiquement `/favicon.ico` même si une autre icône est spécifiée, ce qui causait l'erreur 404.
## Impact
- Erreurs 404 répétées dans les logs du serveur
- Aucun favicon affiché dans les onglets du navigateur
- Expérience utilisateur dégradée
## Correctifs
1. Création d'un fichier `favicon.svg` minimal dans `public/`
2. Mise à jour des références dans les fichiers pour pointer vers `/favicon.svg` au lieu de `/favicon.ico`
3. Ajout du type MIME `image/svg+xml` dans les balises `<link>`
## Modifications
- **Fichier créé** : `public/favicon.svg` (SVG minimal avec un rectangle cyan)
- **Fichiers modifiés** :
- `components/HomeView.tsx` : `<link rel="icon" href="/favicon.svg" type="image/svg+xml" />`
- `pages/presentation.tsx` : `<link rel="icon" href="/favicon.svg" type="image/svg+xml" />`
- `pages/docs.tsx` : `<link rel="icon" href="/favicon.svg" type="image/svg+xml" />`
## Modalités de déploiement
1. Les modifications sont dans le code source
2. Le fichier `favicon.svg` est servi automatiquement par Next.js depuis le dossier `public/`
3. Rebuild de l'application : `npm run build`
4. Redémarrage du service Next.js si nécessaire
5. Aucune configuration supplémentaire nécessaire
## Modalités d'analyse
Pour vérifier si le problème existe :
1. Vérifier la présence du fichier `public/favicon.svg`
2. Vérifier les références dans les fichiers HTML (balises `<link rel="icon">`)
3. Tester dans le navigateur : accéder à `/favicon.svg` et vérifier qu'il est servi
4. Vérifier les logs du serveur pour les erreurs 404 sur `/favicon.ico`
## Notes
- Les navigateurs modernes supportent les SVG comme favicon
- SVG est plus léger et plus flexible qu'un fichier .ico
- Si nécessaire, on peut créer un fichier `.ico` en plus pour la compatibilité avec les anciens navigateurs
- Le favicon SVG actuel est minimal (rectangle cyan) et peut être remplacé par un design plus élaboré si nécessaire

View File

@ -0,0 +1,63 @@
# Problème : API NIP-95 upload retourne 500
## Date
2025-01-27
## Problème
L'endpoint API `/api/nip95-upload` retournait une erreur 500 lors des tentatives d'upload de fichiers via NIP-95.
## Symptômes
- Erreur 500 lors des appels à `/api/nip95-upload?endpoint=https://void.cat/upload`
- Console navigateur : `Failed to load resource: the server responded with a status of 500 ()`
- Console navigateur : `NIP-95 upload endpoint error: Object`
- Les uploads NIP-95 échouaient systématiquement
## Root cause
Le package npm `form-data` n'est pas compatible avec `fetch()` natif de Node.js (Node 18+). L'API utilisait `form-data` de npm avec `fetch()` natif, ce qui causait une incompatibilité car :
- `form-data` (npm) utilise des streams Node.js natifs et l'API `getHeaders()` pour obtenir les headers avec le boundary
- `fetch()` natif de Node.js attend le FormData du web standard (disponible dans le navigateur), pas le package npm `form-data`
- Cette incompatibilité provoquait une erreur lors de l'exécution de la requête HTTP
## Impact
- Impossible d'uploader des fichiers via NIP-95
- Les utilisateurs ne pouvaient pas publier d'articles avec des médias
- Fonctionnalité d'upload complètement non fonctionnelle
## Correctifs
1. Remplacement de `fetch()` natif par les modules natifs `https` et `http` de Node.js
2. Utilisation de `form-data.pipe()` pour envoyer les données via les modules natifs
3. Gestion correcte des erreurs et nettoyage des fichiers temporaires dans tous les cas
## Modifications
- **Fichier modifié** : `pages/api/nip95-upload.ts`
- **Imports ajoutés** :
```typescript
import https from 'https'
import http from 'http'
import { URL } from 'url'
```
- **Imports supprimés** :
```typescript
import { Readable } from 'stream' // Non utilisé
```
- **Remplacement de fetch()** : Utilisation de `https.request()` ou `http.request()` selon le protocole de l'URL cible
- **Gestion des erreurs améliorée** : Nettoyage des fichiers temporaires même en cas d'erreur de requête
- **Gestion des streams** : Utilisation de `formData.pipe(proxyRequest)` pour envoyer les données
## Modalités de déploiement
1. Les modifications sont dans le code source
2. Rebuild de l'application : `npm run build`
3. Redémarrage du service Next.js
4. Aucune dépendance supplémentaire nécessaire (utilisation des modules natifs Node.js)
## Modalités d'analyse
Pour vérifier si le problème existe :
1. Vérifier les logs du serveur pour les erreurs liées à `form-data` ou `fetch`
2. Tester l'upload via l'interface utilisateur
3. Vérifier les logs de la console navigateur pour les erreurs 500
4. Vérifier que `form-data` (npm) est utilisé avec `https`/`http` natif, pas avec `fetch()`
## Notes
- Le package `form-data` (npm) doit être utilisé avec les modules `https`/`http` natifs de Node.js, pas avec `fetch()`
- `fetch()` natif de Node.js est compatible avec FormData du web standard (disponible dans le navigateur), pas avec le package npm `form-data`
- Les fichiers temporaires créés par formidable doivent être nettoyés même en cas d'erreur pour éviter l'accumulation de fichiers

View File

@ -2,7 +2,9 @@ import { NextApiRequest, NextApiResponse } from 'next'
import { IncomingForm, File as FormidableFile } from 'formidable'
import FormData from 'form-data'
import fs from 'fs'
import { Readable } from 'stream'
import https from 'https'
import http from 'http'
import { URL } from 'url'
const MAX_FILE_SIZE = 50 * 1024 * 1024 // 50MB
@ -45,7 +47,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
})
})
const { fields, files } = parseResult
const { files } = parseResult
// Get the file from the parsed form
const fileField = files.file?.[0]
@ -61,25 +63,60 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
contentType: fileField.mimetype || 'application/octet-stream',
})
// Forward to target endpoint
let response: Response
// Forward to target endpoint using https/http native modules
const targetUrl = new URL(targetEndpoint)
const isHttps = targetUrl.protocol === 'https:'
const clientModule = isHttps ? https : http
let response: { statusCode: number; statusMessage: string; body: string }
try {
response = await fetch(targetEndpoint, {
response = await new Promise<{ statusCode: number; statusMessage: string; body: string }>((resolve, reject) => {
const headers = formData.getHeaders()
const requestOptions = {
hostname: targetUrl.hostname,
port: targetUrl.port || (isHttps ? 443 : 80),
path: targetUrl.pathname + targetUrl.search,
method: 'POST',
body: formData as unknown as BodyInit,
headers: {
...formData.getHeaders(),
},
headers: headers,
}
const proxyRequest = clientModule.request(requestOptions, (proxyResponse) => {
let body = ''
proxyResponse.setEncoding('utf8')
proxyResponse.on('data', (chunk) => {
body += chunk
})
} catch (fetchError) {
proxyResponse.on('end', () => {
resolve({
statusCode: proxyResponse.statusCode || 500,
statusMessage: proxyResponse.statusMessage || 'Internal Server Error',
body: body,
})
})
proxyResponse.on('error', (error) => {
reject(error)
})
})
proxyRequest.on('error', (error) => {
reject(error)
})
formData.on('error', (error) => {
reject(error)
})
formData.pipe(proxyRequest)
})
} catch (requestError) {
// Clean up temporary file before returning error
try {
fs.unlinkSync(fileField.filepath)
} catch (unlinkError) {
console.error('Error deleting temp file:', unlinkError)
}
const errorMessage = fetchError instanceof Error ? fetchError.message : 'Unknown fetch error'
console.error('NIP-95 proxy fetch error:', {
const errorMessage = requestError instanceof Error ? requestError.message : 'Unknown request error'
console.error('NIP-95 proxy request error:', {
targetEndpoint,
error: errorMessage,
fileSize: fileField.size,
@ -97,20 +134,34 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
console.error('Error deleting temp file:', unlinkError)
}
if (!response.ok) {
const errorText = await response.text()
if (response.statusCode < 200 || response.statusCode >= 300) {
const errorText = response.body.substring(0, 200) // Limit log size
console.error('NIP-95 proxy response error:', {
targetEndpoint,
status: response.status,
statusText: response.statusText,
errorText: errorText.substring(0, 200), // Limit log size
status: response.statusCode,
statusText: response.statusMessage,
errorText: errorText,
})
return res.status(response.status).json({
error: errorText || `Upload failed: ${response.status} ${response.statusText}`,
return res.status(response.statusCode).json({
error: errorText || `Upload failed: ${response.statusCode} ${response.statusMessage}`,
})
}
let result: unknown
try {
result = JSON.parse(response.body)
} catch (parseError) {
const errorMessage = parseError instanceof Error ? parseError.message : 'Invalid JSON response'
console.error('NIP-95 proxy JSON parse error:', {
targetEndpoint,
error: errorMessage,
bodyPreview: response.body.substring(0, 100),
})
return res.status(500).json({
error: `Invalid upload response: ${errorMessage}`,
})
}
const result = await response.json()
return res.status(200).json(result)
} catch (error) {
console.error('NIP-95 proxy error:', error)

View File

@ -46,7 +46,7 @@ export default function DocsPage() {
<title>{t('nav.documentation')} - zapwall.fr</title>
<meta name="description" content="Documentation complète pour zapwall.fr" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
</Head>
<main className="min-h-screen bg-cyber-darker">
<PageHeader />

View File

@ -37,7 +37,7 @@ function PresentationLayout() {
content={t('presentation.description')}
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
</Head>
<main className="min-h-screen bg-cyber-darker">

4
public/favicon.svg Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<rect width="16" height="16" fill="#00ffff"/>
</svg>

After

Width:  |  Height:  |  Size: 178 B