Fastly Compute : Fichiers .well-known Interceptés par la Fallback SPA
Problème
Lors de l'utilisation de @fastly/compute-js-static-publish avec la fallback SPA configurée (spaFile: "/index.html"), la méthode PublisherServer.serveRequest() retourne index.html avec le statut 200 et le Content-Type text/html pour N'IMPORTE QUEL chemin non trouvé dans le KV store. Cela inclut /.well-known/apple-app-site-association et /.well-known/assetlinks.json, que iOS et Android exigent d'être servis en tant que application/json.
Symptômes :
- Les Universal Links iOS ne fonctionnent pas (la vérification d'Apple récupère
.well-known/apple-app-site-associationet obtient du HTML) - Les App Links Android ne fonctionnent pas (le vérificateur de Google récupère
.well-known/assetlinks.jsonet obtient du HTML) curl -I https://yourdomain.com/.well-known/apple-app-site-associationafficheContent-Type: text/html- Les fichiers SONT publiés vers KV (confirmé par
npm run fastly:publish) mais retournent quand même du HTML
Causes Racines Non Évidentes
-
La fallback SPA retourne 200, pas 404 : Le mode SPA du publisher retourne
index.htmlavec le HTTP 200 pour les chemins manquants. Vous ne pouvez pas distinguer « fichier servi » de « fallback servie » par le code de statut seul — vous devez inspecter le Content-Type. -
apple-app-site-associationn'a pas d'extension de fichier : L'éditeur statique déduit le type MIME de l'extension de fichier. Sans extension, il ne peut pas détecterapplication/json, donc même si le fichier EST publié vers KV, il peut obtenirapplication/octet-streamou être servi incorrectement. -
includeWellKnown: truedanspublish-content.config.jsest nécessaire mais pas suffisant : Cela garantit que les fichiers sont téléchargés vers KV, mais n'empêche pas la fallback SPA d'intercepter les demandes, et ne corrige pas le content-type pour les fichiers sans extension. -
Les gestionnaires du domaine apex et des sous-domaines ont besoin du correctif : Si votre gestionnaire Compute a des chemins de code séparés pour les sous-domaines par rapport à l'apex, les deux chemins doivent intercepter les demandes
/.well-known/avant d'atteindre la fallback SPA.
Solution
Étape 1 : Assurer que les Fichiers sont Publiés
Dans publish-content.config.js, confirmez que includeWellKnown: true est défini :
// publish-content.config.js
module.exports = {
// ...
includeWellKnown: true, // Doit être true pour inclure les fichiers /.well-known/
// ...
};
Puis publiez le contenu statique :
npm run fastly:publish
Étape 2 : Intercepter les Chemins .well-known Avant la Fallback SPA
Dans votre point d'entrée Compute (compute-js/src/index.js), ajoutez un gestionnaire .well-known
AVANT tout appel à publisherServer.serveRequest(request) qui a la fallback SPA activée.
Garde critique : Vérifiez que la réponse du publisher n'est PAS text/html — si elle l'est,
la fallback SPA a été déclenchée (fichier non dans KV), donc retournez 404 à la place du HTML.
// Dans votre fonction handleRequest principale, AVANT l'appel final à publisherServer.serveRequest() :
// Gérer les demandes .well-known (doit venir avant la fallback SPA)
if (url.pathname.startsWith('/.well-known/')) {
// Gérer d'abord les points de terminaison dynamiques .well-known comme NIP-05
if (url.pathname === '/.well-known/nostr.json') {
return handleNip05(url); // Votre gestionnaire personnalisé
}
// Pour tous les autres fichiers .well-known : récupérer du publisher statique
const wkResponse = await publisherServer.serveRequest(request);
// CRITIQUE : Protection contre la fallback SPA. Le publisher retourne index.html (text/html)
// pour les fichiers non dans KV. Nous devons détecter cela et retourner 404 à la place.
if (
wkResponse != null &&
wkResponse.status === 200 &&
!wkResponse.headers.get('Content-Type')?.includes('text/html')
) {
const headers = new Headers(wkResponse.headers);
// Définir explicitement le type de contenu correct.
// apple-app-site-association n'a pas d'extension, donc le publisher peut ne pas détecter JSON.
const isJsonFile =
url.pathname.endsWith('.json') ||
url.pathname.endsWith('/apple-app-site-association') ||
url.pathname === '/.well-known/apple-app-site-association';
headers.set(
'Content-Type',
isJsonFile ? 'application/json' : (headers.get('Content-Type') || 'application/octet-stream')
);
headers.set('Cache-Control', 'public, max-age=3600');
headers.append('Vary', 'X-Original-Host'); // Si vous utilisez un routage multi-service
return new Response(wkResponse.body, { status: 200, headers });
}
// Fichier non dans KV (ou publisher a retourné la fallback SPA) — retourner un 404 approprié
return new Response('Not Found', { status: 404 });
}
Étape 3 : Appliquer le Même Correctif dans les Gestionnaires de Sous-domaine
Si vous avez un traitement séparé pour les demandes de sous-domaine, ajoutez la même protection aussi. Les chemins de sous-domaine passent par une branche de code différente avant d'atteindre le gestionnaire du domaine apex :
if (subdomain) {
if (url.pathname.startsWith('/.well-known/')) {
if (url.pathname === '/.well-known/nostr.json') {
return handleSubdomainNip05(subdomain);
}
// Même motif : intercepter, protection contre la fallback SPA, forcer le type de contenu JSON
const wkResponse = await publisherServer.serveRequest(request);
if (
wkResponse != null &&
wkResponse.status === 200 &&
!wkResponse.headers.get('Content-Type')?.includes('text/html')
) {
const headers = new Headers(wkResponse.headers);
const contentType =
url.pathname.endsWith('.json') || url.pathname.endsWith('/apple-app-site-association')
? 'application/json'
: headers.get('Content-Type') || 'application/octet-stream';
headers.set('Content-Type', contentType);
headers.set('Cache-Control', 'public, max-age=3600');
return new Response(wkResponse.body, { status: 200, headers });
}
return new Response('Not Found', { status: 404 });
}
// ... reste du traitement des sous-domaines
}
Vérification
# Doit retourner application/json, PAS text/html
curl -sI https://yourdomain.com/.well-known/apple-app-site-association | grep -i content-type
# Doit retourner un corps JSON
curl -s https://yourdomain.com/.well-known/apple-app-site-association | head -c 100
# assetlinks.json pour Android
curl -sI https://yourdomain.com/.well-known/assetlinks.json | grep -i content-type
# Vérifier que la protection de la fallback SPA fonctionne (chemin qui N'EXISTE PAS dans KV)
curl -sI https://yourdomain.com/.well-known/nonexistent-file
# Doit retourner 404, pas 200
Exemple Complet Fonctionnant
Depuis compute-js/src/index.js dans divine-web :
// 4. Gérer les demandes .well-known
if (url.pathname.startsWith('/.well-known/')) {
// 4a. NIP-05 depuis le KV store
if (url.pathname === '/.well-known/nostr.json') {
return await handleNip05(url);
}
// 4b. Servir les autres fichiers .well-known (apple-app-site-association, assetlinks.json)
// Ceux-ci doivent être servis en tant que JSON, pas la fallback SPA.
// apple-app-site-association n'a pas d'extension de fichier, donc l'éditeur statique
// ne peut pas détecter son type de contenu — nous le gérons explicitement ici.
const wkResponse = await publisherServer.serveRequest(request);
// Protection : si le publisher retourne text/html, c'est la fallback SPA, pas le vrai fichier
if (wkResponse != null && wkResponse.status === 200 && !wkResponse.headers.get('Content-Type')?.includes('text/html')) {
const headers = new Headers(wkResponse.headers);
// Assurer le type de contenu correct pour les fichiers d'association d'app
const contentType = url.pathname.endsWith('.json') || url.pathname.endsWith('/apple-app-site-association')
? 'application/json'
: headers.get('Content-Type') || 'application/octet-stream';
headers.set('Content-Type', contentType);
headers.set('Cache-Control', 'public, max-age=3600');
headers.append('Vary', 'X-Original-Host');
return new Response(wkResponse.body, {
status: 200,
headers,
});
}
// Fichier non trouvé dans KV — retourner 404 au lieu de la fallback SPA
return new Response('Not Found', { status: 404 });
}
Checklist de Déploiement
Après avoir effectué les modifications de code :
# 1. Publier d'abord le contenu statique (télécharge les fichiers .well-known vers KV)
npm run fastly:publish
# 2. Déployer le code du worker edge (avec la logique d'interception .well-known)
npm run fastly:deploy
# REMARQUE : L'ordre a de l'importance si les fichiers n'étaient pas dans KV auparavant. Si vous déployez d'abord le code,
# il retournera correctement 404 pour les fichiers manquants. Puis la publication télécharge les fichiers.
# N'importe quel ordre fonctionne — la protection gère les deux cas.
Notes
- Ce motif s'applique à tout service Fastly Compute utilisant
@fastly/compute-js-static-publishavecspaFileconfiguré. - La fallback SPA est intentionnelle pour le routage côté client, mais elle casse tout chemin qui a besoin d'un vrai 404 (comme les fichiers de vérification
.well-known). - La détection du content-type par extension de fichier est une limitation fondamentale de la publication statique — les fichiers sans extension ont toujours besoin d'un traitement explicite.
- Si vous servez plusieurs domaines (apex + sous-domaines), chaque chemin de code qui peut appeler
publisherServer.serveRequest()a besoin de la protection d'interception.well-known. - Après
fastly:publish, attendez jusqu'à 2-3 minutes pour la propagation KV avant de tester.